1 Introduction

Support Vector Machines (SVM) are a powerful and versatile set of supervised learning algorithms widely used for classification, regression, and outlier detection tasks. It is particularly well-suited for problems involving complex, high-dimensional data and is robust against overfitting.

Binary Classification: SVM is widely used to classify data into two distinct classes by finding the best decision boundary (hyperplane) that separates the classes with the maximum margin.

Multi-class Classification: SVM can handle multi-class problems by combining multiple binary classifiers using techniques like one-vs-one or one-vs-all.

Regression (SVR - Support Vector Regression): SVM can predict continuous outputs by finding a hyperplane that approximates the target variable within a margin of tolerance.

Outlier Detection: SVM can detect anomalies by identifying data points that deviate significantly from the main distribution.

SVM is a distribution-free method for both classification and regression. Linear support vector machines are commonly used in practice. However, depending on the complexity of the practical problems, non-linear SVMs are also frequently used.

This module focuses on linear SVM and its applications in regression and classification. The kernel SVM will also be introduced to handle nonlinear classification and regression problems.

2 How SVM Works?

To help gain an intuitive understanding of SVM, we consider the binary classification task of SVVM in two-dimensional feature space using a subset of the well-known iris data set.

# iris data
iris0 <- iris[c(1:50, 101:150), c("Sepal.Length", "Sepal.Width","Species" )]
iris0 <-iris0[which(iris0$Sepal.Length> 5 & iris0$Sepal.Width>2.5),]
## support vectors
cvData = data.frame(xx=c(5.5,5.4,5.6,6.2), 
                    yy=c(3.5,3.4,2.8,3.4), 
                    pt=c("A", "B", "C", "D"))
##
svmgg <- ggplot(data = iris0, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
         geom_point(shape=19,  size=3) + 
         geom_point(data =cvData, aes(x=xx, y=yy), shape =19, size=2, color = "darkred") +
         annotate("text", 
                  x = c(5.5,5.4,5.6,6.2)+0.04, 
                  y = c(3.5,3.4,2.8,3.4)-0.04, 
                  label = c("A", "B", "C", "D"),
                  size = 3.3,
                  color = "darkred") +
         scale_x_continuous(minor_breaks = seq(5, 8, 0.1)) +
         scale_y_continuous(minor_breaks = seq(2.5, 4.5, 0.1)) +
         geom_abline(intercept = 1, 
                     slope = 0.4, 
                     color="lightseagreen", 
                     linetype=1, 
                     size=0.5) +
         geom_abline(intercept = -6, 
                     slope = 1.7, 
                     color="indianred3", 
                     linetype=1, 
                     size=0.5) +
         geom_abline(intercept = -2.8, 
                     slope = 1, 
                     color="blue", 
                     linetype=2, 
                     size=0.75) +
         geom_label( label="L1: y = x - 2.8", 
                     x=7.3,
                     y=4.4,
                     label.padding = unit(0.25, "lines"), # Rectangle size around label
                     label.size = 0.15,
                     color = "blue",
                     fill="white" ) +
         geom_abline(intercept = -2, 
                     slope = 1, 
                     color="blue", 
                     linetype=2, 
                     size=0.75) +
         geom_label( label="L2: y = x - 2", 
                     x=6.1,
                     y=4.1,
                     label.padding = unit(0.25, "lines"), # Rectangle size around label
                     label.size = 0.15,
                     color = "blue",
                     fill="white" ) +
         # margin
         geom_segment(x = 5.9, y = 3.9, xend = 6.3, yend = 3.5,
                     arrow = arrow(length = unit(0.02, "npc"), ends = "both"),
                     color = "darkgreen", size = 0.85)  +
         # linear decision boundary
         geom_abline(intercept = -2.4, 
                     slope = 1, 
                     color="darkred", 
                     linetype=1, 
                     size=0.75) +
         geom_label( label="L3: y = x - 2.4", 
                     x=6.6,
                     y=4.25,
                     label.padding = unit(0.25, "lines"), # Rectangle size around label
                     label.size = 0.15,
                     color = "darkred",
                     fill="white" ) +  
         # annotation
         annotate(geom = "text", x = 6.05, y = 3.85, 
                  label = "Margin", 
                  color = "darkgreen",
                  size = 3.1,
                  angle = -42) +
         #
         geom_label( label="L4: y = 0.4x + 1", 
                     x=7.5,
                     y=4,
                     label.padding = unit(0.25, "lines"), # Rectangle size around label
                     label.size = 0.1,
                     color = "lightseagreen",
                     fill="white" ) +
          geom_label( label="L5: y = 1.7x - 6", 
                     x=6.1,
                     y=4.4,
                     label.padding = unit(0.25, "lines"), # Rectangle size around label
                     label.size = 0.05,
                     color = "indianred3",
                     fill="white" ) +
         ggtitle("Key Terms in SVM") + 
         theme( axis.line = element_line(size = .5, color = "red"),
                panel.grid.major = element_line(color = "gray", size = (.2)),
                panel.grid.minor = element_line(size = (0.2), color = "gray"),
                panel.background = element_blank(),
                plot.margin = unit(c(1,0,1,1), "cm"),
                plot.title = element_text(hjust=0.5,
                                          vjust = 5,
                                          family = "sans", 
                                          size = 16),
                legend.position = c(0.9, 0.45),
                legend.background = element_rect(fill="white",
                                                 size=0.5, 
                                                 linetype="solid", 
                                                 colour ="darkblue"),
                aspect.ratio=0.55)
svmgg

#ggplotly(svmgg)

Feature Space: A feature space is spanned by a set of feature variables (independent variable, predictor variable). In the above figure. The dependent variable (not considered as a feature variable) is Species. Sepal.Length and Sepal.Width are the two feature variables. The two-dimensional coordinate system shown in the above figure is called two-dimensional feature space. The dimension of feature space is equal to the number of (uncorrelated) feature variables.

Hyperplane: The hyperplane is a decision boundary that separates classes in the feature space. In the above 2D figure, the hyperplane is any straight line that separates the two Species. In general, the dimension of a hyperplane in an n-dimensional feature space is \(n-1\).

Margin: The margin is the distance between the two parallel hyper-planes that separate the two classes of the response variable. In the above figure, the parallel hyperplanes \(L1\) and \(L2\) completely separate the two classes.

Support Vectors: Support vectors are the points closest to the decision boundary, not on it. These points are located on the margins that define the maximum-margin hyperplane. The decision boundary itself is positioned equidistant between these margins, and the support vectors are the data points that determine the margin width.

SVM Algorithm: Maximize the margin while minimizing misclassification errors.

3 The Kernel Trick

Kernels are a fundamental concept in machine learning, particularly in algorithms like Support Vector Machines (SVMs), Kernel PCA, and Gaussian Processes. They allow us to implicitly map data into a higher-dimensional space, enabling the modeling of complex, nonlinear relationships without explicitly computing the transformation.

3.1 Kernel Function

A kernel is a function \(K(x,y)\) that computes the similarity between two data points \(x\) and \(y\) in a high-dimensional space. The key idea is to avoid explicitly transforming the data into that high-dimensional space, which can be computationally expensive. Instead, kernels compute the inner product in that space directly.

3.1.1 Inner Product

For two vectors u and v in an inner product space, their inner product is denoted as:

\[ \langle \mathbf{u},\mathbf{v}\rangle \]

The inner product must satisfy the following properties:

  • Linearity: \(\langle a\mathbf{u} + b\mathbf{v},\mathbf{w}\rangle=a\langle \mathbf{u},\mathbf{w} \rangle + b\langle \mathbf{v}, \mathbf{w}\rangle\)

  • Conjugate Symmetry: \(\langle \mathbf{u},\mathbf{v}\rangle=\overline{\langle \mathbf{v},\mathbf{u}\rangle}\)

  • Positivity: \(\langle \mathbf{u},\mathbf{u}\rangle \ge 0\), and \(\langle \mathbf{u},\mathbf{v}\rangle=0\) if and only if \(\mathbf{u}=\mathbf{0}.\)

As an example, dot product in \(\mathbb{R}^n\) is an inner product:

\[ \mathbf{u} = (u_1, u_2, \cdots, u_k), \ \ \mathbf{v} = (v_1, v_2, \cdots, v_k). \] The inner product (dot product) of \(mathbf{u}\) and \(\mathbf{v}\) is

\[ z = \langle \mathbf{u}, \mathbf{v} \rangle = u_1v_1 + u_2v_2 + \cdots + u_kv_k. \] Numerical example, \(\mathbf{u} = (1,2)\) and \(\mathbf{v} = (4,-5)\), then

\[ z = \langle \mathbf{u}, \mathbf{v} \rangle = 1\times 4 + 2\times(-5) = 4-10 = -6. \]

3.1.2 SVM Default RBF Kernel [Optional]

Among the many available kernels, the RBF Kernel (Radial Basis Function), also known as the Gaussian kernel, is the most commonly used in practice. This kernel is highly popular because it can effectively handle both linear and non-linear decision boundaries in the original input space. The decision function \(f(x)\), when evaluated over a grid of points in the input space, is mapped into a higher-dimensional feature space. This mapping creates a lifted surface, which represents the decision boundary in the transformed space.

Here, we derive the mathematical expression for the RBF-lifted surface and explain its components.

1. RBF Kernel Function

The RBF kernel is defined as

\[ K(\mathbf{x}_i, \mathbf{x}_j) = \exp\left( -\gamma ||\mathbf{x}_i - \mathbf{x}_j ||^2\right) \]

where

  • \(\mathbf{x}_i, \mathbf{x}_j\) are input feature vectors.
  • \(\gamma\) is the hyperparameter controlling the influence of each training sample.
  • \(||\mathbf{x}_i - \mathbf{x}_j ||^2\) is the squared Euclidean distance between \(\mathbf{x}_i\) and \(\mathbf{x}_j\).

2. SVM Decision Boundary

The decision function for an SVM with the RBF kernel is:

\[ f(\mathbf{x}) = \sum_{i=1}^K\alpha_i y_i K(\mathbf{x}_i, \mathbf{x}) + b \]

where

  • \(N\) is the number of support vectors (i.e., the points on the decision boundary).
  • \(\alpha_i\) are the Lagrange multipliers (non-zero only for support vectors).
  • \(y_i\) are class labels (\(+1\), or \(-1\)).
  • \(b\) is the bias term.

Substituting the RBF kernel into the decision function, we get

\[ f(\mathbf{x}) = \sum_{i=1}^K\alpha_i y_i \exp\left(-\gamma ||\mathbf{x}_i - \mathbf{x}||^2\right) + b \]

3. RBF Lifted Surface

The RBF lifted surface is the evaluation of the decision function \(f(x)\) over a grid of points in the input space. For a 2D input space (\(x=[x_1,x_2]\)), the lifted surface is a 3D surface where:

  • The \(x_1\) and \(x_2\) axes represent the input feature space.
  • The z-axis represents the value of the decision function \(f(x)\).

The mathematical expression of the lifted surface is

\[ z = f(x_1, x_2) = \sum_{i=1}^N \alpha_i y_i \exp\left(-\gamma [(x_1-x_{i1})^2+(x_2-x_{i2})^2]\right) + b. \]

where

  • \((x_{i1}, x_{i2})\) are the coordinates of the i-th support vector.
  • \((x_{1}, x_{2})\) are the coordinates of a point in the input space.
  • \(\alpha_i\) are the Lagrange multipliers (non-zero only for support vectors).
  • \(y_i\) are class labels (\(+1\), or \(-1\)).
  • \(b\) is the bias term.

4. Visualizing the Lifted Surface

To visualize the lifted surface

  • Create a grid of points \((x_1, x_2)\) in the input space.
  • Evaluate \(f(x_1, x_2)\) at each grid point.
  • Plot the surface \(z=f(x_1, x_2)\) in 3D.

Graphical representation of RBF with 2D input feature space.

3.1.3 Other Common Kernel Functions

  • Linear Kernel: This is the simplest kernel, which computes the dot product of the input vectors. It is used when the data is already linearly separable. The explicit form is given by

\[ K(x,y)=\mathbf{x}^T\mathbf{y} \]

  • Polynomial Kernel: This kernel computes the similarity between vectors using a polynomial function. The degree \(d\) and the constant \(c\) are parameters that control the shape of the decision boundary. Its function is given by

\[ K(x,y)=(\mathbf{x}^T\mathbf{y}+c)^d \]

  • Radial Basis Function (RBF) Kernel (Gaussian Kernel): This kernel is one of the most popular and is used when the decision boundary is expected to be highly nonlinear. The parameter \(\sigma\) controls the width of the Gaussian.

\[ K(x,y)=\exp \left(-\frac{||\mathbf{x}-\mathbf{y}||^2}{2\sigma^2}\right) \]

  • Sigmoid Kernel: This kernel is similar to the activation function used in neural networks and can be used in SVMs to create a decision boundary that resembles a neural network.

\[ K(x,y)=\text{tanh}(\alpha \mathbf{x}^T\mathbf{y}+c) \]

3.2 Applications of Kernel Functions

Kernel functions have been used in various applications in machine learning algorithms such as SVMs and PCAs. Here are a few benefits:

  • Handling Nonlinearity: Kernels allow algorithms to learn complex, nonlinear relationships in the data by implicitly mapping the data to a higher-dimensional space where linear separation is possible. We will use an example to illustrate these applications in the next subsection.

  • Computational Efficiency: Kernels enable the computation of inner products in high-dimensional spaces without explicitly computing the coordinates in that space, which can be computationally expensive.

  • Flexibility: Different kernels can be chosen based on the specific problem and the nature of the data, allowing for a high degree of customization.

3.3 A Simulated Demonstrative Example

We use an example to illustrate how to utilize a kernel function to lift a low-dimensional problem into a high-dimensional space in R. We’ll use a simple example of classifying non-linearly separable data using a Support Vector Machine (SVM) with a kernel function.

3.3.1 Visual Illustration

Suppose we have a dataset in 2D space that is not linearly separable. We can use a kernel function to map this data into a higher-dimensional space where it becomes linearly separable.

Generate Non-linearly Separable Data

We first generate 300 non-linearly separable data in 2D space using a bivariate normal distribution with mean vector \(\mathbf{\mu}=c(10, 20)\) and variance-covariance matrix

\[ \mathbf{\Sigma} = \begin{bmatrix} 5 & 3 \\ 3 & 6 \end{bmatrix} = \begin{bmatrix} (\sqrt{5})^2 & \rho \sqrt{5}\sqrt{6} \\ \rho \sqrt{5}\sqrt{6} & (\sqrt{6})^2 \end{bmatrix}, \]

where \(\rho = \sqrt{0.3}\). The corresponding density surface is given below.

#library(MASS)
set.seed(123)
###
bvnData <- mvrnorm(
  n = 300,                      # sample size
  mu = c(10, 20),               # local mean vector
  Sigma = matrix(c(5, 3, 3, 6), ncol = 2)      # local covariate matrix
  )
#####
cols <- hcl.colors(10, "viridis")
#####
x10 <- bvnData[,1]
x20 <- bvnData[,2]
###
mu <- c(10, 20)                # global mean vector
sigma <- matrix(c(5, 3, 3, 6), ncol = 2)      # global covariate matrix   
ff <- function(x1, x2) dmnorm(cbind(x1, x2), mu, sigma)   # Density function
ff0 <- function(x1, x2) {0.025 + 0*x1 + 0*x2}   # Density function
###
### plot the points in the 2D feature space
y <- ifelse(ff(x10,x20) > 0.025, 1, 0)     # define the label of the 2D points
data <- data.frame(x1 = x10, x2 = x20, y = as.factor(y))  # y label
data0 <- data[data$y == 0, ]   # data with label 0
data1 <-data[data$y == 1, ]    # data with label 1
## Preparing plots of data points with label 0 and label 1 separately
x1.0 <- data0$x1   
x2.0 <- data0$x2   
x1.1 <- data1$x1
x2.1 <- data1$x2
### plot the bivariate surface
# Create a grid of points covering the feature space
grid_resolution <- 0.1          # grid size
x1_range <- seq(min(data$x1), max(data$x1), by = grid_resolution)
x2_range <- seq(min(data$x2), max(data$x2), by = grid_resolution)
# Convert to a matrix for plotly surface
z_matrix <- outer(x1_range,x2_range, ff)   # height of grid point on the surface
z0 <- outer(x1_range, x2_range, ff0)       # intersecting plane for obtaining curve 
                                           # decision boundary
# 3D Surface Plot
plot_ly(x = x1_range, 
        y = x2_range, 
        z = ~z_matrix, 
        type = "surface",
        contours = list( z = list(show = TRUE, 
                                   color = "white",       # set contour color
                                   width = 2,             # set contour thickness
                                  start = min(z_matrix ), 
                                  end = max(z_matrix ), 
                                  size = 0.0125)))   %>%
       layout(title = "Bivariate Scatter Plot",
              scene = list(xaxis = list(title = "x1"),
              yaxis = list(title = "x2"),
              zaxis = list(title = "Density")),
              showlegend = FALSE
         ) %>% hide_colorbar() 

The 2D scatter plot with points colored according to the labels is given below.

###
plot(data$x1, data$x2, col = ifelse(data$y == 1, cols[10], cols[1]), pch = 19, main = "Original Data")

The yellow points in the middle cannot be separated by a straight line in the original two-dimensional feature space. However, by projecting the data into a three-dimensional space, they can be separated using a two-dimensional plane, forming a linear decision boundary, as illustrated in the following figure.

#library(MASS)
#set.seed(123)
#######
## function to calculate the third coordinates of the bivariate density surface
ff <- function(x1, x2) dmnorm(cbind(x1, x2), mu, sigma) # Density function
### function to calculate the third coordinates of the intersecting plane
ff0 <- function(x1, x2) {0.025 + 0*x1 + 0*x2}   # Density function
#######
### plot the points in the 2D feature space
y <- ifelse(ff(x10,x20) > 0.025, 1, 0)     # define the label of the 2D points
### working data set
bn.data <- data.frame(x1 = x10, x2 = x20, y = as.factor(y))   # y is response label
### points with label y = 0
bn.data0 <- bn.data[bn.data$y == 0, ]
bn.x1.0 <- bn.data0$x1 
bn.x2.0 <- bn.data0$x2
### points with label y = 1
bn.data1 <-bn.data[bn.data$y == 1, ]
bn.x1.1 <- bn.data1$x1
bn.x2.1 <- bn.data1$x2
### plot the bivariate surface
# Create a grid of points covering the feature space
grid_resolution <- 0.1          # grid size
x1_range <- seq(min(bn.data$x1), max(bn.data$x1), by = grid_resolution)
x2_range <- seq(min(bn.data$x2), max(bn.data$x2), by = grid_resolution)
# Convert to matrix for plotly surface
## third coordinates of the bivariate normal density surface
z_matrix <- outer(x1_range,x2_range, ff)   # height of grid point on the surface
z0 <- outer(x1_range, x2_range, ff0)       # intersecting plane for obtaining curve 
##                                           # decision boundary
## 3D Surface Plot
plot_ly()    %>%
         # adding intersecting plane
         add_trace(x =  x1_range,    
                   y =  x2_range, 
                   z =  z0,
                   colors = "skyblue",
                   type="surface",
                   opacity = 0.5)   %>%
         # adding points with label y = 0
         add_trace(x = bn.x1.0,    
                   y = bn.x2.0, 
                   z = ff(bn.x1.0, bn.x2.0), 
                   mode = "markers", 
                   type = "scatter3d",#"scatter3d", 
                   marker = list(size = 3, 
                                 color = cols[1], 
                                 symbol = 104)) %>%
         # adding points with label y =1
         add_trace(x = bn.x1.1, 
                   y = bn.x2.1, 
                   z = ff(bn.x1.1, bn.x2.1),
                   mode = "markers", 
                   type = "scatter3d", 
                   marker = list(size = 3, 
                                 color = cols[10], 
                                 symbol = 104)) %>%
       layout(title = "Bivariate Scatter Plot",
              scene = list(xaxis = list(title = "x1"),
              yaxis = list(title = "x2"),
              zaxis = list(title = "Density")),
              showlegend = FALSE
         )%>% hide_colorbar() 

The nonlinear curve formed by the intersection of the 3D surface and the 2D plane represents the decision boundary. Its projection onto the original 2D feature space defines the nonlinear separation boundary.

3.3.2 Kernel Density Estimation Approach

For the separation problem in a two-dimensional feature space, nonparametric density estimation methods can be used to estimate the density surface. The optimal nonlinear separation boundary can then be identified using the contours of the estimated density surface. Below is an example based on the same simulated data from the previous subsection.

surface = kde2d(bn.data$x1, bn.data$x2, n = 100)
plot_ly(x = surface$x, 
        y = surface$y,
        z = surface$z,
        type = "surface",
        contours = list( z = list(show = TRUE, 
                                   color = "white",       # set contour color
                                   width = 2,             # set contour thickness
                                  start = min(z_matrix ), 
                                  end = max(z_matrix ), 
                                  size = 0.0125)))   %>%
         # adding intersecting plane
         add_trace(x =  x1_range,    
                   y =  x2_range, 
                   z =  z0,
                   colors = "skyblue",
                   type="surface",
                   opacity = 0.5)  %>% hide_colorbar() 

We can see that the estimated density surface is slightly different from the true density surface of the bivariate normal distribution in the previous subsection. The following contour plot shows the potential nonlinear separation boundary.

#library(MASS)
####
#par(mfrow=c(1,2))
cols <- hcl.colors(10, "viridis")
plot(bn.data$x1, bn.data$x2, col = ifelse(bn.data$y == 1, cols[10], cols[1]), pch = 19,
     xlab = "x1", ylab = "x2", main = "Bivariate Kernel Density Estimated Surface with Contours")

contour(kde2d(bn.data$x1, bn.data$x2, n = 100), add = TRUE,
        level = 0.0125, lwd = 2, col = "purple4", cex = 2)
contour(kde2d(bn.data$x1, bn.data$x2, n = 100), add = TRUE,
        level = 0.025, lwd = 2, col = "gold", cex = 2)

The nonparametric approach offers flexible estimation but is subject to potential misspecification. In this illustrative example, a bivariate normal (Gaussian) kernel was used as a weighting function to compute the weighted average of all points, defining the density (moving average) in three-dimensional space. However, in support vector machines (SVM), the Gaussian kernel serves a different purpose: it maps a lower-dimensional feature space to a higher-dimensional space, enabling the transformation of a nonlinear separation problem into a linear one.


3.3.3 SVM with RBF Kernel

We now use R svm() in the R library e1071 to find the decision (separation) boundary of the above simulated data set.

#library(e1071)
#library(ggplot2)

# Train SVM with RBF kernel
svm_model <- svm(y ~ ., data = bn.data, kernel = "radial", gamma = 1)
##
# Define a grid over the feature space
x1_range <- seq(min(bn.data$x1), max(bn.data$x1), length = 55)
x2_range <- seq(min(bn.data$x2), max(bn.data$x2), length = 55)
grid0 <- expand.grid(x1 = x1_range, x2 = x2_range)
####
#### Predict decision values
svm.pred <- predict(svm_model, grid0, decision.values = TRUE)
# turn over the surface for easier reading. add a negative sign to the pred values.
decision_values <- - attributes(svm.pred)$decision.values   
grid0$decision_values <- decision_values
zz_matrix <- matrix(grid0$decision_values, byrow = TRUE, ncol= length(x1_range))
#############
#############
### 3D data points plotting 
fitted.svm <- predict(svm_model, bn.data[,-3], decision.values = TRUE)
# reverse predicted z for data points
fitted.y.val <- - attributes(fitted.svm)$decision.values   
# data$fitted.y.val <- fitted.y.val
data.points <- data.frame(x1 = bn.data$x1, x2 = bn.data$x2, pred.y =fitted.y.val )

### plot the points in the 2D feature space
y <- ifelse(fitted.y.val  > 0, 1, 0)     # define the label of the 2D points
### working data set # y is response label
svm.data <- data.frame(x1 = x10, x2 = x20, zz = fitted.y.val, y = as.factor(y))   
### points with label y = 0
svm.data0 <- svm.data[svm.data$y == 0, ]
svm.x1.0 <- svm.data0$x1 
svm.x2.0 <- svm.data0$x2
svm.z.0 <- svm.data0$X0.1
### points with label y = 1
svm.data1 <-svm.data[svm.data$y == 1, ]
svm.x1.1 <- svm.data1$x1
svm.x2.1 <- svm.data1$x2
svm.z.1 <- svm.data1$X0.1
#############
### Separating plane
ff0 <- function(x1, x2) {0 + 0*x1 + 0*x2}   # Density function
z0 <- outer(x1_range, x2_range, ff0) 

###
# Extract specific contour (e.g., at level z = 0.5)
contour_data <- contourLines(x1_range, x2_range, zz_matrix, levels = 0)

# Convert contour data to a data frame
contour_df <- do.call(rbind, lapply(contour_data, function(cl) {
  data.frame(x = cl$x, y = cl$y, z = rep(0.5, length(cl$x))) # Constant z level
}))
############

## 3D Surface Plot
plot_ly(x = x1_range, 
        y = x2_range,
        z = zz_matrix, 
        type = "surface",
        contours = list( z = list(show = TRUE, 
                                  color = "brown",       # set contour color
                                  width = 4,             # set contour thickness
                                  #start = min(zz_matrix ), 
                                  #end = max(zz_matrix ), 
                                  levels = 0)))   %>%
#)    %>%
         # adding intersecting plane
         add_trace(x =  x1_range,    
                   y =  x2_range, 
                   z =  z0,
                   colors = "steelblue",
                   type="surface",
                   opacity = 0.5)   %>%
         # adding points with label y = 0
         add_trace(x = svm.x1.0,    
                   y = svm.x2.0, 
                   z = svm.z.0, 
                   mode = "markers", 
                   type = "scatter3d",#"scatter3d", 
                   marker = list(size = 3, 
                                 color = cols[1], 
                                 symbol = 104)) %>%
         # adding points with label y =1
         add_trace(x = svm.x1.1, 
                   y = svm.x2.1, 
                   z = svm.z.1,
                   mode = "markers", 
                   type = "scatter3d", 
                   marker = list(size = 3, 
                                 color = cols[10], 
                                 symbol = 104)) %>%
       layout(title = "SVM Predicted 3D Scatter Plot",
              scene = list(xaxis = list(title = "x1"),
              yaxis = list(title = "x2"),
              zaxis = list(title = "Density")),
              showlegend = FALSE
         ) %>% hide_colorbar() 

The nonlinear decision boundary is depicted in the following figure.

ggsvm <- ggplot(data, aes(x1, x2, color = y)) +
  geom_point(size = 2) +
  scale_color_manual(values = c("0" = cols[1], "1" = cols[10])) +
  labs(title = "SVM Classification with RBF",
       x = "X-axis",
       y = "Y-axis",
       color = "Category") +
  geom_contour(data = grid0, aes(x = x1, y = x2, z = as.numeric(decision_values)), 
               breaks = 0, color = "purple4") +
  theme_minimal()
ggplotly(ggsvm)

4 Handing Categorical Features

The examples and illustrations of SVM discussed earlier are based on numerical features. When dealing with categorical features, we need to convert them into numerical representations. Below are some common techniques for handling categorical features in SVM.

4.1 One-Hot Encoding

One-hot encoding converts each categorical feature into a binary vector, where each category is represented as a binary column.

An illustrative example in R:

# Load necessary library
#library(e1071)  # For SVM
#library(caret)  # For data preprocessing

# Example dataset
data <- data.frame(
  feature1 = c("A", "B", "A", "C"),
  feature2 = c("X", "Y", "X", "Z"),
  target = c(1, 0, 1, 0)
)

# One-hot encoding
dummy <- dummyVars(" ~ .", data = data)
data_encoded <- data.frame(predict(dummy, newdata = data))
# View encoded data
print(data_encoded)
  feature1A feature1B feature1C feature2X feature2Y feature2Z target
1         1         0         0         1         0         0      1
2         0         1         0         0         1         0      0
3         1         0         0         1         0         0      1
4         0         0         1         0         0         1      0
# Train SVM
#svm_model <- svm(target ~ ., data = data_encoded, kernel = "linear")
#summary(svm_model)

One-hot encoding is the most common method but can lead to high dimensionality for features with many categories.

4.2 Label Encoding

Label encoding assigns a unique integer to each category. This is useful for ordinal categorical features.

An illustrative example in R:

# Example dataset
data <- data.frame(
  feature1 = c("Low", "Medium", "High", "Low"),
  feature2 = c("Small", "Large", "Medium", "Small"),
  target = c(1, 0, 1, 0)
)

# Label encoding
data$feature1 <- as.numeric(factor(data$feature1, levels = c("Low", "Medium", "High")))
data$feature2 <- as.numeric(factor(data$feature2, levels = c("Small", "Medium", "Large")))

# View encoded data
print(data)
  feature1 feature2 target
1        1        1      1
2        2        3      0
3        3        2      1
4        1        1      0
# Train SVM
#svm_model <- svm(target ~ ., data = data, kernel = "linear")
#summary(svm_model)

Label encoding is suitable for ordinal data but may introduce unintended ordinality for nominal data.

4.3 Frequency Encoding

Frequency encoding replaces each category with its frequency in the dataset.

An illustrative example in R:

# Example dataset
data <- data.frame(
  feature1 = c("A", "B", "A", "C"),
  feature2 = c("X", "Y", "X", "Z"),
  target = c(1, 0, 1, 0)
)

# Frequency encoding
freq_encoding <- function(column) {
  freq <- table(column)
  return(as.numeric(freq[column]))
}

data$feature1 <- freq_encoding(data$feature1)
data$feature2 <- freq_encoding(data$feature2)

# View encoded data
print(data)
  feature1 feature2 target
1        2        2      1
2        1        1      0
3        2        2      1
4        1        1      0
# Train SVM
#svm_model <- svm(target ~ ., data = data, kernel = "linear")
#summary(svm_model)

Frequency encoding and target encoding are useful for reducing dimensionality but may lead to overfitting if not regularized.

4.4 Target Encoding

Target encoding replaces each category with the mean of the target variable for that category. This is useful for supervised learning tasks.

An illustrative example in R:

# Example dataset
data <- data.frame(
  feature1 = c("A", "B", "A", "C"),
  feature2 = c("X", "Y", "X", "Z"),
  target = c(1, 0, 1, 0)
)

# Target encoding
target_encoding <- function(column, target) {
  mean_target <- tapply(target, column, mean)
  return(mean_target[column])
}

data$feature1 <- target_encoding(data$feature1, data$target)
data$feature2 <- target_encoding(data$feature2, data$target)

# View encoded data
print(data)
  feature1 feature2 target
1        1        1      1
2        0        0      0
3        1        1      1
4        0        0      0
# Train SVM
#svm_model <- svm(target ~ ., data = data, kernel = "linear")
#summary(svm_model)

5 SVM Classification Algorithms

Hard margin and soft margin are indeed core concepts in Support Vector Machines (SVM), and they determine how strictly the model enforces the separation of data points into their respective classes. They are central to understanding how SVMs handle linearly separable and non-linearly separable data.

For ease of presentation, we use the linear SVM for classification as an example to explain the concept of soft and hard margins and the formulation of SVM. Note that the distance between two parallel hyperplanes in a k-dimensional space, denoted by

\[ L_1: \ a_1 x_1 +a_2 x_2 + \cdots + a_k x_k + d_1 = 0 \] and

\[ L_2: \ a_1 x_1 +a_2 x_2 + \cdots + a_k x_k + d_2 = 0, \]

is given by

\[ |L_1L_2| = \frac{|d_2-d_1|}{\sqrt{a_1^2 + a_2^2 + \cdots + a_k^2}} \]

5.1 Hard Margin SVM

Without loss of generality, we consider the linear separation problem in 2 dimensional feature space.

The goal is to find the hyperplane that maximizes the margin (the distance between the two parallel hyperplanes and the nearest data points from each class) while ensuring that all data points are correctly classified. In the two dimensional feature space, the two parallel hyperplanes are denoted by respectively

\[ w_1x_1 + w_2 x_2 + b = +1 \ \ \text{and} \ \ w_1x_1 + w_2x_2 + b = -1. \]

The coefficients of \(x_1\) and \(x_2\) of two expressions are identical because they are parallel. With the above non-standard notation, the hyperplane \(w_1x_1 + w_2 x_2 + b = +1\) is closer to the group with label \(y = +1\), while \(w_1x_1 + w_2 x_2 + b = -1\) is closer to the group with label \(y = -1\). Consequently, \(w_1x_1 + w_2 x_2 + b = +1\) is referred to as the Positive Margin Hyperplane and \(w_1x_1 + w_2 x_2 + b = -1\) as the Negative Margin Hyperplane.

If we introduce the following column vector notations

\[ \mathbf{w} = \begin{bmatrix} w_1 \\ w_2 \end{bmatrix} \ \ \text{and} \ \ \mathbf{x} = \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}, \]

we can re-express in the following vector form

\[ \mathbf{w^Tx} + b = 1 \ \ \text{and} \ \ \mathbf{w^Tx} + b = -1. \]

Using the above notations, we summarize the positive and negative hyperplanes in the following figure.

include_graphics("img/HardMargin.png")

Based on the formula of distance between two parallel lines (i.e., the 2D hyperplanes), the problem of maximizing the margin \(2/||\mathbf{w}||_2\) is expressed in the following equivalent expressions

\[ \max_{\mathbf{w},b}\frac{2}{||\mathbf{w}||^2_2} \rightarrow \max_{\mathbf{w},b}\frac{1}{||\mathbf{w}||^2_2} \rightarrow \min_{\mathbf{w},b}||\mathbf{w}||^2_2. \]

That is, the above optimization problem is to estimate \(\mathbf{w} = (w_1, w_2)\) and \(b\) that maximize the margin \(2/||\mathbf{w}||_2\) or minimize \(||\mathbf{w}||_2\). This optimization will be performed using software programs such as R function svm() in R library MASS.

Once \(\mathbf{w} = (w_1, w_2)\) and \(b\) are estimated from the data, the center hyperplane between the two parallel hyperplanes, the desired decision boundary - \(\mathbf{w^Tx} + b = 0\) , is uniquely determined.

Finally, the objective function of maximizing hard-margin in SVM is given by

\[ \min_{\mathbf{w},b} ||\mathbf{w}||^2 \]

subjecting to the constraint that all data points are correctly classified

\[ y_i(\mathbf{w}^T\mathbf{x}_i + b) \ge 1 \ \ \text{for any} \ \ i. \]

where \(y_i\) is the label of \(i\)-th data point taking values \(+1\) or \(-1\).


5.2 Soft Margin: C-classification

Soft margin SVM is used when the data is not linearly separable or contains noise. It allows for some misclassification by introducing slack variables (\(\xi_i\)) that measure the degree of misclassification for each data point. The goal is to maximize the margin while minimizing the sum of these slack variables.

include_graphics("img/SoftMargin.png")

We can see from the above figure that \(0 < \xi_1 < 1\) and \(\xi_2, \xi_3 > 1\).

The estimation of the unknown parameters in the SVM algorithm is the following optimization.

\[ \min_{\mathbf{w},b} \left( ||\mathbf{w}||^2 + C\sum_{i=1}^n \xi_i\right) \]

Subject to the constraint that all data points are correctly classified

\[ y_i(\mathbf{w}^T\mathbf{x}_i + b) \ge 1 - \xi_i \ \ \text{for any} \ \ i. \]

where \(\xi_i \ge 0\) and \(y_i\) is the label of \(i\)-th data point taking values \(+1\) or \(-1\). \(C\) is a regularization parameter that controls the trade-off between maximizing the margin and minimizing the classification error.

The slack variable \(xi_i\) is a measure of how much a point violates the hard margin constraint.

  • Correctly classified points outside the margin will have \(\xi_i = 0\).

  • Correctly classified points within the margin will have \(0 < \xi_i < 1\).

  • Misclassified points will have \(\xi_i >1\).

The support vector machine based on maximizing soft margin is also called C-Classification to highlight the importance of the regularization parameter \(C\).

The regularization parameter \(C\) in SVM is indeed a hyperparameter that needs to be tuned to achieve the best performance for a specific dataset. It is not something that is learned during training but rather set before training begins.

  • \(C\) controls the penalty for misclassified or margin-violating data points in soft margin SVM.
  • A larger \(C\) imposes a higher penalty for misclassifications, leading to a narrower margin and stricter classification (closer to hard margin SVM).
  • A smaller \(C\) allows more misclassifications, resulting in a wider margin and a more flexible model.

Proper tuning of \(C\) is crucial because it directly influences the trade-off between maximizing the margin and minimizing classification errors.

5.3 \(\nu\) - Classification

Unlike C-classification SVM in which the trade-off between maximizing the margin and minimizing misclassification errors is controlled by the regularization parameter \(C\), In \(\nu\)-SVM, the trade-off is controlled by the parameter \(\nu\). The objective function is defined as

\[ \min_{\mathbf{w}, b, \mathbf{\xi}, \rho} \left( ||\mathbf{w}||^2 - \nu \rho +\frac{1}{N}\sum_{i=1}^N \xi_i\right) \] subjects constraints

\[ y_i(\mathbf{w}^T \mathbf{x}_i + b) \ge \rho - \mathbf{\xi}_i, \ \ \mathbf{\xi}_i \ge 0, \ \ \text{and} \ \ \rho > 0, \]

where

  • \(||\mathbf{w}||^2\) reflect the margin as in C-SVM,
  • \(-\nu \rho\) adjusts the margin width \(\rho\),
  • \(-\sum_{i=1}^N\xi_i/N\) penalizes misclassifications (slack variables \(\xi_i\)).

\(\nu\) is the hyperparameter in \(\nu\)-classification SVM that needs to be tuned through various methods including cross-validation. It controls the fraction of margin errors and support vectors.

  • \(\nu \in (0,1]\)
  • A small \(\nu\) results in a wider margin but allows more misclassifications.
  • A large \(\nu\) results in a narrower margin but allows fewer misclassifications.

5.4 C-SVM v.s. \(\nu\)-SVM

There is a theoretical relationship between \(C\)-SVM and \(\nu\)-SVM. For a given dataset, there exists a mapping between \(C\) and \(\nu\) such that the solutions of C-SVM and \(\nu\)-SVM are equivalent. However, this mapping depends on the dataset and is not straightforward to compute.

C-SVM and \(\nu\)-SVM can produce the same result if:

  • The parameters C and \(\nu\) are chosen such that they correspond to the same trade-off between margin size and classification errors.

  • The dataset and kernel are the same.

  • The optimization algorithm converges to the same solution.

In practice, this equivalence is rarely exploited because \(\nu\)-SVM provides a more intuitive way to control the fraction of support vectors and errors, while C-SVM requires tuning C through cross-validation or other methods.

For the convenience of comparison, we make the following table to show the difference between the two support vector machines.

C-Classification \(\nu\)-Classification
Hyperparameter Uses \(C\) (cost parameter). Uses \(\nu\) (nu parameter).
Interpretation \(C\) controls the penalty for miss-classifications. \(\nu\) controls the fraction of support vectors and margin errors.
Range C can take any positive value (typically \(C>0\)). \(\nu\) ranges between 0 and 1 (\(0<\nu \le 1\)).
Focus Focuses on minimizing classification errors. Focuses on controlling the fraction of support vectors and margin errors.
Margin Control Fixed margin Adaptive margin (\(\rho\))
Interpretability Less intuitive More intuitive
Ease of Tuning Requires careful tuning of C to balance bias and variance. Easier to interpret, as \(\nu\) directly relates to the fraction of support vectors.
Use Case General-purpose Tasks requiring control over support vectors

\(\nu\)-SVM is a powerful and interpretable variant of SVM that provides explicit control over the fraction of support vectors and margin errors. Its intuitive parameterization and adaptive margin make it a valuable tool for classification, regression, and outlier detection tasks.


5.5 SVM with R

Support Vector Machines (SVM) are a popular set of machine learning algorithms used for classification, regression, and outlier detection. In R, several libraries provide implementations of SVM, each with its own features and capabilities. We will use two of the most commonly used libraries: e1071 and kernlab. A recently developed R library caret includes some of the R functions in the aforementioned libraries with some additional convenient functions for performing machine learning tasks.

  • e1071: General-purpose SVM for classification and regression tasks. It is one of the most widely used packages for SVM in R that Supports various kernel functions (linear, polynomial, radial basis, sigmoid) and provides tools for model tuning and cross-validation.
    • svm(): Fits an SVM model for classification, regression, or novelty detection.
    • tune.svm(): Performs hyperparameter tuning (e.g., cost, gamma) using grid search.
  • kernlab: Advanced SVM tasks with custom kernels or specialized requirements. It is a comprehensive package for kernel-based machine learning, including SVM. It offers a wide range of kernel functions and is highly flexible.
    • ksvm(): Fits an SVM model with support for multiple kernel types.
    • kernelMatrix(): Computes the kernel matrix for custom kernels.
  • caret: Streamlined SVM modeling with automated tuning and evaluation. It is a meta-package for machine learning that provides a unified interface for various models, including SVM. It uses e1071 or kernlab under the hood and simplifies model training and evaluation and integrates with other machine learning workflows.
    • train(): Fits an SVM model with hyperparameter tuning and cross-validation.
LS0tDQp0aXRsZTogJ1N1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSk6IENvbmNlcHRzIGFuZCBGb3JtdWxhdGlvbnMnDQphdXRob3I6ICJDaGVuZyBQZW5nIg0KZGF0ZTogIldlc3QgQ2hlc3RlciBVbml2ZXJzaXR5Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGZpZ193aWR0aDogMw0KICAgIGZpZ19oZWlnaHQ6IDMNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCmBgYHs9aHRtbH0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KLyogQ2FzY2FkaW5nIFN0eWxlIFNoZWV0cyAoQ1NTKSBpcyBhIHN0eWxlc2hlZXQgbGFuZ3VhZ2UgdXNlZCB0byBkZXNjcmliZSB0aGUgcHJlc2VudGF0aW9uIG9mIGEgZG9jdW1lbnQgd3JpdHRlbiBpbiBIVE1MIG9yIFhNTC4gaXQgaXMgYSBzaW1wbGUgbWVjaGFuaXNtIGZvciBhZGRpbmcgc3R5bGUgKGUuZy4sIGZvbnRzLCBjb2xvcnMsIHNwYWNpbmcpIHRvIFdlYiBkb2N1bWVudHMuICovDQoNCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtc2l6ZTogMjJweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IG5hdnk7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXNpemU6IDIwcHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCA0IHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCi5oaWdobGlnaHRtZSB7IGJhY2tncm91bmQtY29sb3I6eWVsbG93OyB9DQoNCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCjwvc3R5bGU+DQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQgDQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCmlmICghcmVxdWlyZSgicGFsbWVycGVuZ3VpbnMiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGFsbWVycGVuZ3VpbnMiKQ0KbGlicmFyeShwYWxtZXJwZW5ndWlucykNCn0NCmlmICghcmVxdWlyZSgicGxvdGx5IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpDQpsaWJyYXJ5KHBsb3RseSkNCn0NCmlmICghcmVxdWlyZSgiZTEwNzEiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQ0KbGlicmFyeShlMTA3MSkNCn0NCmlmICghcmVxdWlyZSgibW1lbG4iKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygibW1lbG4iKQ0KbGlicmFyeShtbWVsbikNCn0NCmlmICghcmVxdWlyZSgiTUFTUyIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikNCmxpYnJhcnkoTUFTUykNCn0NCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCn0NCmlmICghcmVxdWlyZSgicGxvdGx5IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpDQpsaWJyYXJ5KHBsb3RseSkNCn0NCmlmICghcmVxdWlyZSgiY2FyZXQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQ0KbGlicmFyeShjYXJldCkNCn0NCiMjICAgIyBGb3IgZGF0YSBwcmVwcm9jZXNzaW5nDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICByZXN1bHRzID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQQ0KICAgICAgICAgICAgICAgICAgICAgICkgIA0KYGBgDQoNClwNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTSkgYXJlIGEgcG93ZXJmdWwgYW5kIHZlcnNhdGlsZSBzZXQgb2Ygc3VwZXJ2aXNlZCBsZWFybmluZyBhbGdvcml0aG1zIHdpZGVseSB1c2VkIGZvciBjbGFzc2lmaWNhdGlvbiwgcmVncmVzc2lvbiwgYW5kIG91dGxpZXIgZGV0ZWN0aW9uIHRhc2tzLiBJdCBpcyBwYXJ0aWN1bGFybHkgd2VsbC1zdWl0ZWQgZm9yIHByb2JsZW1zIGludm9sdmluZyBjb21wbGV4LCBoaWdoLWRpbWVuc2lvbmFsIGRhdGEgYW5kIGlzIHJvYnVzdCBhZ2FpbnN0IG92ZXJmaXR0aW5nLg0KDQoNCioqQmluYXJ5IENsYXNzaWZpY2F0aW9uKio6IFNWTSBpcyB3aWRlbHkgdXNlZCB0byBjbGFzc2lmeSBkYXRhIGludG8gdHdvIGRpc3RpbmN0IGNsYXNzZXMgYnkgZmluZGluZyB0aGUgYmVzdCBkZWNpc2lvbiBib3VuZGFyeSAoaHlwZXJwbGFuZSkgdGhhdCBzZXBhcmF0ZXMgdGhlIGNsYXNzZXMgd2l0aCB0aGUgbWF4aW11bSBtYXJnaW4uDQoNCioqTXVsdGktY2xhc3MgQ2xhc3NpZmljYXRpb24qKjogU1ZNIGNhbiBoYW5kbGUgbXVsdGktY2xhc3MgcHJvYmxlbXMgYnkgY29tYmluaW5nIG11bHRpcGxlIGJpbmFyeSBjbGFzc2lmaWVycyB1c2luZyB0ZWNobmlxdWVzIGxpa2Ugb25lLXZzLW9uZSBvciBvbmUtdnMtYWxsLg0KDQoqKlJlZ3Jlc3Npb24gKFNWUiAtIFN1cHBvcnQgVmVjdG9yIFJlZ3Jlc3Npb24pKio6ICBTVk0gY2FuIHByZWRpY3QgY29udGludW91cyBvdXRwdXRzIGJ5IGZpbmRpbmcgYSBoeXBlcnBsYW5lIHRoYXQgYXBwcm94aW1hdGVzIHRoZSB0YXJnZXQgdmFyaWFibGUgd2l0aGluIGEgbWFyZ2luIG9mIHRvbGVyYW5jZS4NCg0KKipPdXRsaWVyIERldGVjdGlvbioqOiBTVk0gY2FuIGRldGVjdCBhbm9tYWxpZXMgYnkgaWRlbnRpZnlpbmcgZGF0YSBwb2ludHMgdGhhdCBkZXZpYXRlIHNpZ25pZmljYW50bHkgZnJvbSB0aGUgbWFpbiBkaXN0cmlidXRpb24uDQoNClNWTSBpcyBhIGRpc3RyaWJ1dGlvbi1mcmVlIG1ldGhvZCBmb3IgYm90aCBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbi4gTGluZWFyIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmVzIGFyZSBjb21tb25seSB1c2VkIGluIHByYWN0aWNlLiBIb3dldmVyLCBkZXBlbmRpbmcgb24gdGhlIGNvbXBsZXhpdHkgb2YgdGhlIHByYWN0aWNhbCBwcm9ibGVtcywgbm9uLWxpbmVhciBTVk1zIGFyZSBhbHNvIGZyZXF1ZW50bHkgdXNlZC4NCg0KVGhpcyBtb2R1bGUgZm9jdXNlcyBvbiBsaW5lYXIgU1ZNIGFuZCBpdHMgYXBwbGljYXRpb25zIGluIHJlZ3Jlc3Npb24gYW5kIGNsYXNzaWZpY2F0aW9uLiBUaGUga2VybmVsIFNWTSB3aWxsIGFsc28gYmUgaW50cm9kdWNlZCB0byBoYW5kbGUgbm9ubGluZWFyIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHByb2JsZW1zLg0KDQoNCiMgSG93IFNWTSBXb3Jrcz8gICANCg0KVG8gaGVscCBnYWluIGFuIGludHVpdGl2ZSB1bmRlcnN0YW5kaW5nIG9mIFNWTSwgd2UgY29uc2lkZXIgdGhlIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiB0YXNrIG9mIFNWVk0gaW4gdHdvLWRpbWVuc2lvbmFsIGZlYXR1cmUgc3BhY2UgdXNpbmcgYSBzdWJzZXQgb2YgdGhlIHdlbGwta25vd24gaXJpcyBkYXRhIHNldC4NCg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTZ9DQojIGlyaXMgZGF0YQ0KaXJpczAgPC0gaXJpc1tjKDE6NTAsIDEwMToxNTApLCBjKCJTZXBhbC5MZW5ndGgiLCAiU2VwYWwuV2lkdGgiLCJTcGVjaWVzIiApXQ0KaXJpczAgPC1pcmlzMFt3aGljaChpcmlzMCRTZXBhbC5MZW5ndGg+IDUgJiBpcmlzMCRTZXBhbC5XaWR0aD4yLjUpLF0NCiMjIHN1cHBvcnQgdmVjdG9ycw0KY3ZEYXRhID0gZGF0YS5mcmFtZSh4eD1jKDUuNSw1LjQsNS42LDYuMiksIA0KICAgICAgICAgICAgICAgICAgICB5eT1jKDMuNSwzLjQsMi44LDMuNCksIA0KICAgICAgICAgICAgICAgICAgICBwdD1jKCJBIiwgIkIiLCAiQyIsICJEIikpDQojIw0Kc3ZtZ2cgPC0gZ2dwbG90KGRhdGEgPSBpcmlzMCwgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTZXBhbC5XaWR0aCwgY29sb3IgPSBTcGVjaWVzKSkgKw0KICAgICAgICAgZ2VvbV9wb2ludChzaGFwZT0xOSwgIHNpemU9MykgKyANCiAgICAgICAgIGdlb21fcG9pbnQoZGF0YSA9Y3ZEYXRhLCBhZXMoeD14eCwgeT15eSksIHNoYXBlID0xOSwgc2l6ZT0yLCBjb2xvciA9ICJkYXJrcmVkIikgKw0KICAgICAgICAgYW5ub3RhdGUoInRleHQiLCANCiAgICAgICAgICAgICAgICAgIHggPSBjKDUuNSw1LjQsNS42LDYuMikrMC4wNCwgDQogICAgICAgICAgICAgICAgICB5ID0gYygzLjUsMy40LDIuOCwzLjQpLTAuMDQsIA0KICAgICAgICAgICAgICAgICAgbGFiZWwgPSBjKCJBIiwgIkIiLCAiQyIsICJEIiksDQogICAgICAgICAgICAgICAgICBzaXplID0gMy4zLA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIpICsNCiAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhtaW5vcl9icmVha3MgPSBzZXEoNSwgOCwgMC4xKSkgKw0KICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKG1pbm9yX2JyZWFrcyA9IHNlcSgyLjUsIDQuNSwgMC4xKSkgKw0KICAgICAgICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMSwgDQogICAgICAgICAgICAgICAgICAgICBzbG9wZSA9IDAuNCwgDQogICAgICAgICAgICAgICAgICAgICBjb2xvcj0ibGlnaHRzZWFncmVlbiIsIA0KICAgICAgICAgICAgICAgICAgICAgbGluZXR5cGU9MSwgDQogICAgICAgICAgICAgICAgICAgICBzaXplPTAuNSkgKw0KICAgICAgICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLTYsIA0KICAgICAgICAgICAgICAgICAgICAgc2xvcGUgPSAxLjcsIA0KICAgICAgICAgICAgICAgICAgICAgY29sb3I9ImluZGlhbnJlZDMiLCANCiAgICAgICAgICAgICAgICAgICAgIGxpbmV0eXBlPTEsIA0KICAgICAgICAgICAgICAgICAgICAgc2l6ZT0wLjUpICsNCiAgICAgICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC0yLjgsIA0KICAgICAgICAgICAgICAgICAgICAgc2xvcGUgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yPSJibHVlIiwgDQogICAgICAgICAgICAgICAgICAgICBsaW5ldHlwZT0yLCANCiAgICAgICAgICAgICAgICAgICAgIHNpemU9MC43NSkgKw0KICAgICAgICAgZ2VvbV9sYWJlbCggbGFiZWw9IkwxOiB5ID0geCAtIDIuOCIsIA0KICAgICAgICAgICAgICAgICAgICAgeD03LjMsDQogICAgICAgICAgICAgICAgICAgICB5PTQuNCwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnBhZGRpbmcgPSB1bml0KDAuMjUsICJsaW5lcyIpLCAjIFJlY3RhbmdsZSBzaXplIGFyb3VuZCBsYWJlbA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAuMTUsDQogICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgIGZpbGw9IndoaXRlIiApICsNCiAgICAgICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC0yLCANCiAgICAgICAgICAgICAgICAgICAgIHNsb3BlID0gMSwgDQogICAgICAgICAgICAgICAgICAgICBjb2xvcj0iYmx1ZSIsIA0KICAgICAgICAgICAgICAgICAgICAgbGluZXR5cGU9MiwgDQogICAgICAgICAgICAgICAgICAgICBzaXplPTAuNzUpICsNCiAgICAgICAgIGdlb21fbGFiZWwoIGxhYmVsPSJMMjogeSA9IHggLSAyIiwgDQogICAgICAgICAgICAgICAgICAgICB4PTYuMSwNCiAgICAgICAgICAgICAgICAgICAgIHk9NC4xLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWwucGFkZGluZyA9IHVuaXQoMC4yNSwgImxpbmVzIiksICMgUmVjdGFuZ2xlIHNpemUgYXJvdW5kIGxhYmVsDQogICAgICAgICAgICAgICAgICAgICBsYWJlbC5zaXplID0gMC4xNSwNCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgZmlsbD0id2hpdGUiICkgKw0KICAgICAgICAgIyBtYXJnaW4NCiAgICAgICAgIGdlb21fc2VnbWVudCh4ID0gNS45LCB5ID0gMy45LCB4ZW5kID0gNi4zLCB5ZW5kID0gMy41LA0KICAgICAgICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMDIsICJucGMiKSwgZW5kcyA9ICJib3RoIiksDQogICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJkYXJrZ3JlZW4iLCBzaXplID0gMC44NSkgICsNCiAgICAgICAgICMgbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5DQogICAgICAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAtMi40LCANCiAgICAgICAgICAgICAgICAgICAgIHNsb3BlID0gMSwgDQogICAgICAgICAgICAgICAgICAgICBjb2xvcj0iZGFya3JlZCIsIA0KICAgICAgICAgICAgICAgICAgICAgbGluZXR5cGU9MSwgDQogICAgICAgICAgICAgICAgICAgICBzaXplPTAuNzUpICsNCiAgICAgICAgIGdlb21fbGFiZWwoIGxhYmVsPSJMMzogeSA9IHggLSAyLjQiLCANCiAgICAgICAgICAgICAgICAgICAgIHg9Ni42LA0KICAgICAgICAgICAgICAgICAgICAgeT00LjI1LA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWwucGFkZGluZyA9IHVuaXQoMC4yNSwgImxpbmVzIiksICMgUmVjdGFuZ2xlIHNpemUgYXJvdW5kIGxhYmVsDQogICAgICAgICAgICAgICAgICAgICBsYWJlbC5zaXplID0gMC4xNSwNCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImRhcmtyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgZmlsbD0id2hpdGUiICkgKyAgDQogICAgICAgICAjIGFubm90YXRpb24NCiAgICAgICAgIGFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSA2LjA1LCB5ID0gMy44NSwgDQogICAgICAgICAgICAgICAgICBsYWJlbCA9ICJNYXJnaW4iLCANCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gImRhcmtncmVlbiIsDQogICAgICAgICAgICAgICAgICBzaXplID0gMy4xLA0KICAgICAgICAgICAgICAgICAgYW5nbGUgPSAtNDIpICsNCiAgICAgICAgICMNCiAgICAgICAgIGdlb21fbGFiZWwoIGxhYmVsPSJMNDogeSA9IDAuNHggKyAxIiwgDQogICAgICAgICAgICAgICAgICAgICB4PTcuNSwNCiAgICAgICAgICAgICAgICAgICAgIHk9NCwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnBhZGRpbmcgPSB1bml0KDAuMjUsICJsaW5lcyIpLCAjIFJlY3RhbmdsZSBzaXplIGFyb3VuZCBsYWJlbA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAuMSwNCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImxpZ2h0c2VhZ3JlZW4iLA0KICAgICAgICAgICAgICAgICAgICAgZmlsbD0id2hpdGUiICkgKw0KICAgICAgICAgIGdlb21fbGFiZWwoIGxhYmVsPSJMNTogeSA9IDEuN3ggLSA2IiwgDQogICAgICAgICAgICAgICAgICAgICB4PTYuMSwNCiAgICAgICAgICAgICAgICAgICAgIHk9NC40LA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWwucGFkZGluZyA9IHVuaXQoMC4yNSwgImxpbmVzIiksICMgUmVjdGFuZ2xlIHNpemUgYXJvdW5kIGxhYmVsDQogICAgICAgICAgICAgICAgICAgICBsYWJlbC5zaXplID0gMC4wNSwNCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImluZGlhbnJlZDMiLA0KICAgICAgICAgICAgICAgICAgICAgZmlsbD0id2hpdGUiICkgKw0KICAgICAgICAgZ2d0aXRsZSgiS2V5IFRlcm1zIGluIFNWTSIpICsgDQogICAgICAgICB0aGVtZSggYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuNSwgY29sb3IgPSAicmVkIiksDQogICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5Iiwgc2l6ZSA9ICguMikpLA0KICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9ICgwLjIpLCBjb2xvciA9ICJncmF5IiksDQogICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygxLDAsMSwxKSwgImNtIiksDQogICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdCA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAic2FucyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDE2KSwNCiAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC40NSksDQogICAgICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0id2hpdGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemU9MC41LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5ldHlwZT0ic29saWQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSJkYXJrYmx1ZSIpLA0KICAgICAgICAgICAgICAgIGFzcGVjdC5yYXRpbz0wLjU1KQ0Kc3ZtZ2cNCiNnZ3Bsb3RseShzdm1nZykNCmBgYA0KDQoqKkZlYXR1cmUgU3BhY2UqKjogQSBmZWF0dXJlIHNwYWNlIGlzIHNwYW5uZWQgYnkgYSBzZXQgb2YgZmVhdHVyZSB2YXJpYWJsZXMgKGluZGVwZW5kZW50IHZhcmlhYmxlLCBwcmVkaWN0b3IgdmFyaWFibGUpLiBJbiB0aGUgYWJvdmUgZmlndXJlLiBUaGUgYGRlcGVuZGVudCB2YXJpYWJsZWAgKG5vdCBjb25zaWRlcmVkIGFzIGEgZmVhdHVyZSB2YXJpYWJsZSkgaXMgYFNwZWNpZXNgLiBgU2VwYWwuTGVuZ3RoYCBhbmQgYFNlcGFsLldpZHRoYCBhcmUgdGhlIHR3byBmZWF0dXJlIHZhcmlhYmxlcy4gVGhlIHR3by1kaW1lbnNpb25hbCBjb29yZGluYXRlIHN5c3RlbSBzaG93biBpbiB0aGUgYWJvdmUgZmlndXJlIGlzIGNhbGxlZCB0d28tZGltZW5zaW9uYWwgZmVhdHVyZSBzcGFjZS4gKipUaGUgZGltZW5zaW9uIG9mIGZlYXR1cmUgc3BhY2UgaXMgZXF1YWwgdG8gdGhlIG51bWJlciBvZiAodW5jb3JyZWxhdGVkKSBmZWF0dXJlIHZhcmlhYmxlcy4qKiANCg0KKipIeXBlcnBsYW5lKio6IFRoZSAqKmh5cGVycGxhbmUqKiBpcyBhIGRlY2lzaW9uIGJvdW5kYXJ5IHRoYXQgc2VwYXJhdGVzIGNsYXNzZXMgaW4gdGhlIGZlYXR1cmUgc3BhY2UuIEluIHRoZSBhYm92ZSAyRCBmaWd1cmUsIHRoZSBgaHlwZXJwbGFuZWAgaXMgYW55ICoqc3RyYWlnaHQgbGluZSoqIHRoYXQgc2VwYXJhdGVzIHRoZSB0d28gYFNwZWNpZXNgLiBJbiBnZW5lcmFsLCB0aGUgZGltZW5zaW9uIG9mIGEgYGh5cGVycGxhbmVgIGluIGFuIG4tZGltZW5zaW9uYWwgYGZlYXR1cmUgc3BhY2VgIGlzICRuLTEkLiANCg0KDQoqKk1hcmdpbioqOiBUaGUgbWFyZ2luIGlzIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gKipwYXJhbGxlbCoqIGh5cGVyLXBsYW5lcyB0aGF0IHNlcGFyYXRlIHRoZSB0d28gY2xhc3NlcyBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUuIEluIHRoZSBhYm92ZSBmaWd1cmUsIHRoZSBwYXJhbGxlbCBoeXBlcnBsYW5lcyAkTDEkIGFuZCAkTDIkIGNvbXBsZXRlbHkgc2VwYXJhdGUgdGhlIHR3byBjbGFzc2VzLg0KDQoqKlN1cHBvcnQgVmVjdG9ycyoqOiBTdXBwb3J0IHZlY3RvcnMgYXJlIHRoZSBwb2ludHMgKipjbG9zZXN0KiogdG8gdGhlIGRlY2lzaW9uIGJvdW5kYXJ5LCA8Zm9udCBjb2xvciA9ICJyZWQiPioqbm90IG9uIGl0Kio8L2ZvbnQ+LiBUaGVzZSBwb2ludHMgYXJlIGxvY2F0ZWQgb24gdGhlIG1hcmdpbnMgdGhhdCBkZWZpbmUgdGhlICoqbWF4aW11bS1tYXJnaW4gaHlwZXJwbGFuZSoqLiBUaGUgZGVjaXNpb24gYm91bmRhcnkgaXRzZWxmIGlzIHBvc2l0aW9uZWQgZXF1aWRpc3RhbnQgYmV0d2VlbiB0aGVzZSBtYXJnaW5zLCBhbmQgdGhlIHN1cHBvcnQgdmVjdG9ycyBhcmUgdGhlIGRhdGEgcG9pbnRzIHRoYXQgZGV0ZXJtaW5lIHRoZSBtYXJnaW4gd2lkdGguDQoNCioqU1ZNIEFsZ29yaXRobSoqOiBNYXhpbWl6ZSB0aGUgbWFyZ2luIHdoaWxlIG1pbmltaXppbmcgbWlzY2xhc3NpZmljYXRpb24gZXJyb3JzLg0KDQoNCg0KDQoNCiMgVGhlIEtlcm5lbCBUcmljaw0KDQoqKktlcm5lbHMqKiBhcmUgYSBmdW5kYW1lbnRhbCBjb25jZXB0IGluIG1hY2hpbmUgbGVhcm5pbmcsIHBhcnRpY3VsYXJseSBpbiBhbGdvcml0aG1zIGxpa2UgKipTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNcykqKiwgKipLZXJuZWwgUENBKiosIGFuZCAqKkdhdXNzaWFuIFByb2Nlc3NlcyoqLiBUaGV5IGFsbG93IHVzIHRvIGltcGxpY2l0bHkgbWFwIGRhdGEgaW50byBhIGhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZSwgZW5hYmxpbmcgdGhlIG1vZGVsaW5nIG9mIGNvbXBsZXgsIG5vbmxpbmVhciByZWxhdGlvbnNoaXBzIHdpdGhvdXQgZXhwbGljaXRseSBjb21wdXRpbmcgdGhlIHRyYW5zZm9ybWF0aW9uLg0KDQoNCiMjIEtlcm5lbCBGdW5jdGlvbg0KDQpBIGtlcm5lbCBpcyBhIGZ1bmN0aW9uICRLKHgseSkkIHRoYXQgY29tcHV0ZXMgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiB0d28gZGF0YSBwb2ludHMgJHgkIGFuZCAkeSQgaW4gYSBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlLiBUaGUga2V5IGlkZWEgaXMgdG8gYXZvaWQgZXhwbGljaXRseSB0cmFuc2Zvcm1pbmcgdGhlIGRhdGEgaW50byB0aGF0IGhpZ2gtZGltZW5zaW9uYWwgc3BhY2UsIHdoaWNoIGNhbiBiZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlLiBJbnN0ZWFkLCBrZXJuZWxzIGNvbXB1dGUgdGhlICoqaW5uZXIgcHJvZHVjdCoqIGluIHRoYXQgc3BhY2UgZGlyZWN0bHkuIA0KDQoNCiMjIyBJbm5lciBQcm9kdWN0DQoNCkZvciB0d28gdmVjdG9ycyB1IGFuZCB2IGluIGFuIGlubmVyIHByb2R1Y3Qgc3BhY2UsIHRoZWlyIGlubmVyIHByb2R1Y3QgaXMgZGVub3RlZCBhczoNCg0KJCQNClxsYW5nbGUgXG1hdGhiZnt1fSxcbWF0aGJme3Z9XHJhbmdsZQ0KJCQNCg0KVGhlIGlubmVyIHByb2R1Y3QgbXVzdCBzYXRpc2Z5IHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczoNCg0KKiBMaW5lYXJpdHk6ICRcbGFuZ2xlIGFcbWF0aGJme3V9ICsgYlxtYXRoYmZ7dn0sXG1hdGhiZnt3fVxyYW5nbGU9YVxsYW5nbGUgXG1hdGhiZnt1fSxcbWF0aGJme3d9IFxyYW5nbGUgKyBiXGxhbmdsZSBcbWF0aGJme3Z9LCBcbWF0aGJme3d9XHJhbmdsZSQNCg0KKiBDb25qdWdhdGUgU3ltbWV0cnk6ICRcbGFuZ2xlIFxtYXRoYmZ7dX0sXG1hdGhiZnt2fVxyYW5nbGU9XG92ZXJsaW5le1xsYW5nbGUgXG1hdGhiZnt2fSxcbWF0aGJme3V9XHJhbmdsZX0kIA0KDQoqIFBvc2l0aXZpdHk6ICRcbGFuZ2xlIFxtYXRoYmZ7dX0sXG1hdGhiZnt1fVxyYW5nbGUgXGdlIDAkLCBhbmQgJFxsYW5nbGUgXG1hdGhiZnt1fSxcbWF0aGJme3Z9XHJhbmdsZT0wJCBpZiBhbmQgb25seSBpZiAkXG1hdGhiZnt1fT1cbWF0aGJmezB9LiQNCg0KDQpBcyBhbiBleGFtcGxlLCAqKmRvdCBwcm9kdWN0KiogaW4gJFxtYXRoYmJ7Un1ebiQgaXMgYW4gaW5uZXIgcHJvZHVjdDoNCg0KJCQNClxtYXRoYmZ7dX0gPSAodV8xLCB1XzIsIFxjZG90cywgdV9rKSwgXCAgXCAgXG1hdGhiZnt2fSA9ICh2XzEsIHZfMiwgXGNkb3RzLCB2X2spLg0KJCQNClRoZSBpbm5lciBwcm9kdWN0IChkb3QgcHJvZHVjdCkgb2YgJG1hdGhiZnt1fSQgYW5kICRcbWF0aGJme3Z9JCBpcw0KDQokJA0KeiA9IFxsYW5nbGUgXG1hdGhiZnt1fSwgXG1hdGhiZnt2fSBccmFuZ2xlID0gdV8xdl8xICsgdV8ydl8yICsgXGNkb3RzICsgdV9rdl9rLg0KJCQNCk51bWVyaWNhbCBleGFtcGxlLCAkXG1hdGhiZnt1fSA9ICgxLDIpJCBhbmQgJFxtYXRoYmZ7dn0gPSAoNCwtNSkkLCB0aGVuIA0KDQokJA0KeiA9IFxsYW5nbGUgXG1hdGhiZnt1fSwgXG1hdGhiZnt2fSBccmFuZ2xlID0gMVx0aW1lcyA0ICsgMlx0aW1lcygtNSkgID0gNC0xMCAgPSAtNi4NCiQkDQoNCiMjIyBTVk0gRGVmYXVsdCBSQkYgS2VybmVsIDxmb250IGNvbG9yID0gInJlZCI+PGI+W09wdGlvbmFsXTwvYj48L2ZvbnQ+DQoNCkFtb25nIHRoZSBtYW55IGF2YWlsYWJsZSBrZXJuZWxzLCB0aGUgUkJGIEtlcm5lbCAoUmFkaWFsIEJhc2lzIEZ1bmN0aW9uKSwgYWxzbyBrbm93biBhcyB0aGUgR2F1c3NpYW4ga2VybmVsLCBpcyB0aGUgbW9zdCBjb21tb25seSB1c2VkIGluIHByYWN0aWNlLiBUaGlzIGtlcm5lbCBpcyBoaWdobHkgcG9wdWxhciBiZWNhdXNlIGl0IGNhbiBlZmZlY3RpdmVseSBoYW5kbGUgYm90aCBsaW5lYXIgYW5kIG5vbi1saW5lYXIgZGVjaXNpb24gYm91bmRhcmllcyBpbiB0aGUgb3JpZ2luYWwgaW5wdXQgc3BhY2UuIFRoZSBkZWNpc2lvbiBmdW5jdGlvbiAkZih4KSQsIHdoZW4gZXZhbHVhdGVkIG92ZXIgYSBncmlkIG9mIHBvaW50cyBpbiB0aGUgaW5wdXQgc3BhY2UsIGlzIG1hcHBlZCBpbnRvIGEgaGlnaGVyLWRpbWVuc2lvbmFsIGZlYXR1cmUgc3BhY2UuIFRoaXMgbWFwcGluZyBjcmVhdGVzIGEgbGlmdGVkIHN1cmZhY2UsIHdoaWNoIHJlcHJlc2VudHMgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IGluIHRoZSB0cmFuc2Zvcm1lZCBzcGFjZS4NCg0KDQpIZXJlLCB3ZSBkZXJpdmUgdGhlIG1hdGhlbWF0aWNhbCBleHByZXNzaW9uIGZvciB0aGUgUkJGLWxpZnRlZCBzdXJmYWNlIGFuZCBleHBsYWluIGl0cyBjb21wb25lbnRzLg0KDQoqKjEuIFJCRiBLZXJuZWwgRnVuY3Rpb24qKg0KDQpUaGUgUkJGIGtlcm5lbCBpcyBkZWZpbmVkIGFzDQoNCiQkDQpLKFxtYXRoYmZ7eH1faSwgXG1hdGhiZnt4fV9qKSA9IFxleHBcbGVmdCggLVxnYW1tYSB8fFxtYXRoYmZ7eH1faSAtIFxtYXRoYmZ7eH1faiB8fF4yXHJpZ2h0KQ0KJCQNCg0Kd2hlcmUgDQoNCiogJFxtYXRoYmZ7eH1faSwgXG1hdGhiZnt4fV9qJCAgYXJlIGlucHV0IGZlYXR1cmUgdmVjdG9ycy4NCiogJFxnYW1tYSQgaXMgdGhlIGh5cGVycGFyYW1ldGVyIGNvbnRyb2xsaW5nIHRoZSBpbmZsdWVuY2Ugb2YgZWFjaCB0cmFpbmluZyBzYW1wbGUuDQoqICR8fFxtYXRoYmZ7eH1faSAtIFxtYXRoYmZ7eH1faiB8fF4yJCBpcyB0aGUgc3F1YXJlZCBFdWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiAkXG1hdGhiZnt4fV9pJCBhbmQgJFxtYXRoYmZ7eH1faiQuIA0KDQoNCioqMi4gU1ZNIERlY2lzaW9uIEJvdW5kYXJ5KioNCg0KVGhlIGRlY2lzaW9uIGZ1bmN0aW9uIGZvciBhbiBTVk0gd2l0aCB0aGUgUkJGIGtlcm5lbCBpczoNCg0KJCQNCmYoXG1hdGhiZnt4fSkgPSBcc3VtX3tpPTF9XktcYWxwaGFfaSB5X2kgSyhcbWF0aGJme3h9X2ksIFxtYXRoYmZ7eH0pICsgYg0KJCQNCg0Kd2hlcmUNCg0KKiAkTiQgaXMgdGhlIG51bWJlciBvZiBzdXBwb3J0IHZlY3RvcnMgKGkuZS4sIHRoZSBwb2ludHMgb24gdGhlIGRlY2lzaW9uIGJvdW5kYXJ5KS4NCiogJFxhbHBoYV9pJCBhcmUgdGhlICoqTGFncmFuZ2UgbXVsdGlwbGllcnMqKiAoPGZvbnQgY29sb3IgPSAicmVkIj5ub24temVybyBvbmx5IGZvciBzdXBwb3J0IHZlY3RvcnM8L2ZvbnQ+KS4NCiogJHlfaSQgYXJlIGNsYXNzIGxhYmVscyAoJCsxJCwgb3IgJC0xJCkuDQoqICRiJCBpcyB0aGUgYmlhcyB0ZXJtLg0KDQpTdWJzdGl0dXRpbmcgdGhlIFJCRiBrZXJuZWwgaW50byB0aGUgZGVjaXNpb24gZnVuY3Rpb24sIHdlIGdldA0KDQokJA0KZihcbWF0aGJme3h9KSA9IFxzdW1fe2k9MX1eS1xhbHBoYV9pIHlfaSBcZXhwXGxlZnQoLVxnYW1tYSB8fFxtYXRoYmZ7eH1faSAtIFxtYXRoYmZ7eH18fF4yXHJpZ2h0KSArIGINCiQkDQoNCioqMy4gUkJGIExpZnRlZCBTdXJmYWNlKioNCg0KVGhlIFJCRiBsaWZ0ZWQgc3VyZmFjZSBpcyB0aGUgZXZhbHVhdGlvbiBvZiB0aGUgZGVjaXNpb24gZnVuY3Rpb24gJGYoeCkkIG92ZXIgYSBncmlkIG9mIHBvaW50cyBpbiB0aGUgaW5wdXQgc3BhY2UuIEZvciBhIDJEIGlucHV0IHNwYWNlICgkeD1beF8xLHhfMl0kKSwgdGhlIGxpZnRlZCBzdXJmYWNlIGlzIGEgM0Qgc3VyZmFjZSB3aGVyZToNCg0KKiBUaGUgJHhfMSQgYW5kICR4XzIkIGF4ZXMgcmVwcmVzZW50IHRoZSBpbnB1dCBmZWF0dXJlIHNwYWNlLg0KKiBUaGUgei1heGlzIHJlcHJlc2VudHMgdGhlIHZhbHVlIG9mIHRoZSBkZWNpc2lvbiBmdW5jdGlvbiAkZih4KSQuDQoNClRoZSBtYXRoZW1hdGljYWwgZXhwcmVzc2lvbiBvZiB0aGUgbGlmdGVkIHN1cmZhY2UgaXMNCg0KJCQNCnogPSBmKHhfMSwgeF8yKSA9IFxzdW1fe2k9MX1eTiBcYWxwaGFfaSB5X2kgXGV4cFxsZWZ0KC1cZ2FtbWEgWyh4XzEteF97aTF9KV4yKyh4XzIteF97aTJ9KV4yXVxyaWdodCkgKyBiLg0KJCQNCg0Kd2hlcmUNCg0KKiAkKHhfe2kxfSwgeF97aTJ9KSQgYXJlIHRoZSBjb29yZGluYXRlcyBvZiB0aGUgaS10aCBzdXBwb3J0IHZlY3Rvci4NCiogJCh4X3sxfSwgeF97Mn0pJCBhcmUgdGhlIGNvb3JkaW5hdGVzIG9mIGEgcG9pbnQgaW4gdGhlIGlucHV0IHNwYWNlLg0KKiAkXGFscGhhX2kkIGFyZSB0aGUgKipMYWdyYW5nZSBtdWx0aXBsaWVycyoqICg8Zm9udCBjb2xvciA9ICJyZWQiPm5vbi16ZXJvIG9ubHkgZm9yIHN1cHBvcnQgdmVjdG9yczwvZm9udD4pLg0KKiAkeV9pJCBhcmUgY2xhc3MgbGFiZWxzICgkKzEkLCBvciAkLTEkKS4NCiogJGIkIGlzIHRoZSBiaWFzIHRlcm0uDQoNCg0KKio0LiBWaXN1YWxpemluZyB0aGUgTGlmdGVkIFN1cmZhY2UqKg0KDQpUbyB2aXN1YWxpemUgdGhlIGxpZnRlZCBzdXJmYWNlDQoNCiogQ3JlYXRlIGEgZ3JpZCBvZiBwb2ludHMgJCh4XzEsIHhfMikkIGluIHRoZSBpbnB1dCBzcGFjZS4NCiogRXZhbHVhdGUgJGYoeF8xLCB4XzIpJCBhdCBlYWNoIGdyaWQgcG9pbnQuDQoqIFBsb3QgdGhlIHN1cmZhY2UgJHo9Zih4XzEsIHhfMikkIGluIDNELg0KDQpHcmFwaGljYWwgcmVwcmVzZW50YXRpb24gb2YgUkJGIHdpdGggMkQgaW5wdXQgZmVhdHVyZSBzcGFjZS4NCg0KYGBge3IgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iOTAlIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9SQkYtS2VybmVsLnBuZyIpDQpgYGANCg0KDQojIyMgT3RoZXIgQ29tbW9uIEtlcm5lbCBGdW5jdGlvbnMNCg0KKiAqKkxpbmVhciBLZXJuZWwqKjogVGhpcyBpcyB0aGUgc2ltcGxlc3Qga2VybmVsLCB3aGljaCBjb21wdXRlcyB0aGUgZG90IHByb2R1Y3Qgb2YgdGhlIGlucHV0IHZlY3RvcnMuIEl0IGlzIHVzZWQgd2hlbiB0aGUgZGF0YSBpcyBhbHJlYWR5IGxpbmVhcmx5IHNlcGFyYWJsZS4gVGhlIGV4cGxpY2l0IGZvcm0gaXMgZ2l2ZW4gYnkNCg0KJCQNCksoeCx5KT1cbWF0aGJme3h9XlRcbWF0aGJme3l9DQokJA0KDQoqICoqUG9seW5vbWlhbCBLZXJuZWwqKjogVGhpcyBrZXJuZWwgY29tcHV0ZXMgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiB2ZWN0b3JzIHVzaW5nIGEgcG9seW5vbWlhbCBmdW5jdGlvbi4gVGhlIGRlZ3JlZSAkZCQgYW5kIHRoZSBjb25zdGFudCAgJGMkIGFyZSBwYXJhbWV0ZXJzIHRoYXQgY29udHJvbCB0aGUgc2hhcGUgb2YgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5LiBJdHMgZnVuY3Rpb24gaXMgZ2l2ZW4gYnkNCg0KJCQNCksoeCx5KT0oXG1hdGhiZnt4fV5UXG1hdGhiZnt5fStjKV5kDQokJA0KDQoqICoqUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIChSQkYpIEtlcm5lbCAoR2F1c3NpYW4gS2VybmVsKSoqOiBUaGlzIGtlcm5lbCBpcyBvbmUgb2YgdGhlIG1vc3QgcG9wdWxhciBhbmQgaXMgdXNlZCB3aGVuIHRoZSBkZWNpc2lvbiBib3VuZGFyeSBpcyBleHBlY3RlZCB0byBiZSBoaWdobHkgbm9ubGluZWFyLiBUaGUgcGFyYW1ldGVyICRcc2lnbWEkIGNvbnRyb2xzIHRoZSB3aWR0aCBvZiB0aGUgR2F1c3NpYW4uDQoNCiQkDQpLKHgseSk9XGV4cCBcbGVmdCgtXGZyYWN7fHxcbWF0aGJme3h9LVxtYXRoYmZ7eX18fF4yfXsyXHNpZ21hXjJ9XHJpZ2h0KSANCiQkDQoNCg0KKiAqKlNpZ21vaWQgS2VybmVsKio6IFRoaXMga2VybmVsIGlzIHNpbWlsYXIgdG8gdGhlIGFjdGl2YXRpb24gZnVuY3Rpb24gdXNlZCBpbiBuZXVyYWwgbmV0d29ya3MgYW5kIGNhbiBiZSB1c2VkIGluIFNWTXMgdG8gY3JlYXRlIGEgZGVjaXNpb24gYm91bmRhcnkgdGhhdCByZXNlbWJsZXMgYSBuZXVyYWwgbmV0d29yay4NCg0KJCQNCksoeCx5KT1cdGV4dHt0YW5ofShcYWxwaGEgXG1hdGhiZnt4fV5UXG1hdGhiZnt5fStjKQ0KJCQNCg0KDQojIyBBcHBsaWNhdGlvbnMgb2YgS2VybmVsIEZ1bmN0aW9ucw0KDQpLZXJuZWwgZnVuY3Rpb25zIGhhdmUgYmVlbiB1c2VkIGluIHZhcmlvdXMgYXBwbGljYXRpb25zIGluIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBzdWNoIGFzIFNWTXMgYW5kIFBDQXMuIEhlcmUgYXJlIGEgZmV3IGJlbmVmaXRzOiANCg0KKiAqKkhhbmRsaW5nIE5vbmxpbmVhcml0eSoqOiAgS2VybmVscyBhbGxvdyBhbGdvcml0aG1zIHRvIGxlYXJuIGNvbXBsZXgsIG5vbmxpbmVhciByZWxhdGlvbnNoaXBzIGluIHRoZSBkYXRhIGJ5ICoqaW1wbGljaXRseSoqIG1hcHBpbmcgdGhlIGRhdGEgdG8gYSBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2Ugd2hlcmUgbGluZWFyIHNlcGFyYXRpb24gaXMgcG9zc2libGUuIFdlIHdpbGwgdXNlIGFuIGV4YW1wbGUgdG8gaWxsdXN0cmF0ZSB0aGVzZSBhcHBsaWNhdGlvbnMgaW4gdGhlIG5leHQgc3Vic2VjdGlvbi4NCg0KKiAqKkNvbXB1dGF0aW9uYWwgRWZmaWNpZW5jeSoqOiBLZXJuZWxzIGVuYWJsZSB0aGUgY29tcHV0YXRpb24gb2YgaW5uZXIgcHJvZHVjdHMgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMgd2l0aG91dCBleHBsaWNpdGx5IGNvbXB1dGluZyB0aGUgY29vcmRpbmF0ZXMgaW4gdGhhdCBzcGFjZSwgd2hpY2ggY2FuIGJlIGNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUuDQoNCiogKipGbGV4aWJpbGl0eSoqOiBEaWZmZXJlbnQga2VybmVscyBjYW4gYmUgY2hvc2VuIGJhc2VkIG9uIHRoZSBzcGVjaWZpYyBwcm9ibGVtIGFuZCB0aGUgbmF0dXJlIG9mIHRoZSBkYXRhLCBhbGxvd2luZyBmb3IgYSBoaWdoIGRlZ3JlZSBvZiBjdXN0b21pemF0aW9uLg0KDQoNCg0KDQoNCg0KIyMgQSBTaW11bGF0ZWQgRGVtb25zdHJhdGl2ZSBFeGFtcGxlDQoNCldlIHVzZSBhbiBleGFtcGxlIHRvIGlsbHVzdHJhdGUgaG93IHRvIHV0aWxpemUgYSBrZXJuZWwgZnVuY3Rpb24gdG8gbGlmdCBhIGxvdy1kaW1lbnNpb25hbCBwcm9ibGVtIGludG8gYSBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlIGluIFIuIFdlJ2xsIHVzZSBhIHNpbXBsZSBleGFtcGxlIG9mIGNsYXNzaWZ5aW5nIG5vbi1saW5lYXJseSBzZXBhcmFibGUgZGF0YSB1c2luZyBhIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSkgd2l0aCBhIGtlcm5lbCBmdW5jdGlvbi4NCg0KIyMjIFZpc3VhbCBJbGx1c3RyYXRpb24NCg0KU3VwcG9zZSB3ZSBoYXZlIGEgZGF0YXNldCBpbiAyRCBzcGFjZSB0aGF0IGlzIG5vdCBsaW5lYXJseSBzZXBhcmFibGUuIFdlIGNhbiB1c2UgYSBrZXJuZWwgZnVuY3Rpb24gdG8gbWFwIHRoaXMgZGF0YSBpbnRvIGEgaGlnaGVyLWRpbWVuc2lvbmFsIHNwYWNlIHdoZXJlIGl0IGJlY29tZXMgbGluZWFybHkgc2VwYXJhYmxlLg0KDQoqKkdlbmVyYXRlIE5vbi1saW5lYXJseSBTZXBhcmFibGUgRGF0YSoqDQoNCldlIGZpcnN0IGdlbmVyYXRlIDMwMCBub24tbGluZWFybHkgc2VwYXJhYmxlIGRhdGEgaW4gMkQgc3BhY2UgdXNpbmcgYSBiaXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiB3aXRoIG1lYW4gdmVjdG9yICRcbWF0aGJme1xtdX09YygxMCwgMjApJCBhbmQgdmFyaWFuY2UtY292YXJpYW5jZSBtYXRyaXggDQoNCiQkDQpcbWF0aGJme1xTaWdtYX0gPSBcYmVnaW57Ym1hdHJpeH0gDQogICAgICAgICAgICAgICAgICAgICAgICA1ICAmICAzIFxcDQogICAgICAgICAgICAgICAgICAgICAgICAzICAmICA2DQogICAgICAgICAgICAgICAgICBcZW5ke2JtYXRyaXh9DQogICAgICAgICAgICAgICAgPSBcYmVnaW57Ym1hdHJpeH0gDQogICAgICAgICAgICAgICAgICAgICAgICAoXHNxcnR7NX0pXjIgICYgIFxyaG8gXHNxcnR7NX1cc3FydHs2fSBcXA0KICAgICAgICAgICAgICAgICAgICAgICAgXHJobyBcc3FydHs1fVxzcXJ0ezZ9ICAmICAoXHNxcnR7Nn0pXjINCiAgICAgICAgICAgICAgICAgIFxlbmR7Ym1hdHJpeH0sDQokJA0KDQp3aGVyZSAkXHJobyA9IFxzcXJ0ezAuM30kLiBUaGUgY29ycmVzcG9uZGluZyBkZW5zaXR5IHN1cmZhY2UgaXMgZ2l2ZW4gYmVsb3cuDQoNCg0KDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0NCiNsaWJyYXJ5KE1BU1MpDQpzZXQuc2VlZCgxMjMpDQojIyMNCmJ2bkRhdGEgPC0gbXZybm9ybSgNCiAgbiA9IDMwMCwgICAgICAgICAgICAgICAgICAgICAgIyBzYW1wbGUgc2l6ZQ0KICBtdSA9IGMoMTAsIDIwKSwgICAgICAgICAgICAgICAjIGxvY2FsIG1lYW4gdmVjdG9yDQogIFNpZ21hID0gbWF0cml4KGMoNSwgMywgMywgNiksIG5jb2wgPSAyKSAgICAgICMgbG9jYWwgY292YXJpYXRlIG1hdHJpeA0KICApDQojIyMjIw0KY29scyA8LSBoY2wuY29sb3JzKDEwLCAidmlyaWRpcyIpDQojIyMjIw0KeDEwIDwtIGJ2bkRhdGFbLDFdDQp4MjAgPC0gYnZuRGF0YVssMl0NCiMjIw0KbXUgPC0gYygxMCwgMjApICAgICAgICAgICAgICAgICMgZ2xvYmFsIG1lYW4gdmVjdG9yDQpzaWdtYSA8LSBtYXRyaXgoYyg1LCAzLCAzLCA2KSwgbmNvbCA9IDIpICAgICAgIyBnbG9iYWwgY292YXJpYXRlIG1hdHJpeCAgIA0KZmYgPC0gZnVuY3Rpb24oeDEsIHgyKSBkbW5vcm0oY2JpbmQoeDEsIHgyKSwgbXUsIHNpZ21hKSAgICMgRGVuc2l0eSBmdW5jdGlvbg0KZmYwIDwtIGZ1bmN0aW9uKHgxLCB4MikgezAuMDI1ICsgMCp4MSArIDAqeDJ9ICAgIyBEZW5zaXR5IGZ1bmN0aW9uDQojIyMNCiMjIyBwbG90IHRoZSBwb2ludHMgaW4gdGhlIDJEIGZlYXR1cmUgc3BhY2UNCnkgPC0gaWZlbHNlKGZmKHgxMCx4MjApID4gMC4wMjUsIDEsIDApICAgICAjIGRlZmluZSB0aGUgbGFiZWwgb2YgdGhlIDJEIHBvaW50cw0KZGF0YSA8LSBkYXRhLmZyYW1lKHgxID0geDEwLCB4MiA9IHgyMCwgeSA9IGFzLmZhY3Rvcih5KSkgICMgeSBsYWJlbA0KZGF0YTAgPC0gZGF0YVtkYXRhJHkgPT0gMCwgXSAgICMgZGF0YSB3aXRoIGxhYmVsIDANCmRhdGExIDwtZGF0YVtkYXRhJHkgPT0gMSwgXSAgICAjIGRhdGEgd2l0aCBsYWJlbCAxDQojIyBQcmVwYXJpbmcgcGxvdHMgb2YgZGF0YSBwb2ludHMgd2l0aCBsYWJlbCAwIGFuZCBsYWJlbCAxIHNlcGFyYXRlbHkNCngxLjAgPC0gZGF0YTAkeDEgICANCngyLjAgPC0gZGF0YTAkeDIgICANCngxLjEgPC0gZGF0YTEkeDENCngyLjEgPC0gZGF0YTEkeDINCiMjIyBwbG90IHRoZSBiaXZhcmlhdGUgc3VyZmFjZQ0KIyBDcmVhdGUgYSBncmlkIG9mIHBvaW50cyBjb3ZlcmluZyB0aGUgZmVhdHVyZSBzcGFjZQ0KZ3JpZF9yZXNvbHV0aW9uIDwtIDAuMSAgICAgICAgICAjIGdyaWQgc2l6ZQ0KeDFfcmFuZ2UgPC0gc2VxKG1pbihkYXRhJHgxKSwgbWF4KGRhdGEkeDEpLCBieSA9IGdyaWRfcmVzb2x1dGlvbikNCngyX3JhbmdlIDwtIHNlcShtaW4oZGF0YSR4MiksIG1heChkYXRhJHgyKSwgYnkgPSBncmlkX3Jlc29sdXRpb24pDQojIENvbnZlcnQgdG8gYSBtYXRyaXggZm9yIHBsb3RseSBzdXJmYWNlDQp6X21hdHJpeCA8LSBvdXRlcih4MV9yYW5nZSx4Ml9yYW5nZSwgZmYpICAgIyBoZWlnaHQgb2YgZ3JpZCBwb2ludCBvbiB0aGUgc3VyZmFjZQ0KejAgPC0gb3V0ZXIoeDFfcmFuZ2UsIHgyX3JhbmdlLCBmZjApICAgICAgICMgaW50ZXJzZWN0aW5nIHBsYW5lIGZvciBvYnRhaW5pbmcgY3VydmUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBkZWNpc2lvbiBib3VuZGFyeQ0KIyAzRCBTdXJmYWNlIFBsb3QNCnBsb3RfbHkoeCA9IHgxX3JhbmdlLCANCiAgICAgICAgeSA9IHgyX3JhbmdlLCANCiAgICAgICAgeiA9IH56X21hdHJpeCwgDQogICAgICAgIHR5cGUgPSAic3VyZmFjZSIsDQogICAgICAgIGNvbnRvdXJzID0gbGlzdCggeiA9IGxpc3Qoc2hvdyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsICAgICAgICMgc2V0IGNvbnRvdXIgY29sb3INCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLCAgICAgICAgICAgICAjIHNldCBjb250b3VyIHRoaWNrbmVzcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0ID0gbWluKHpfbWF0cml4ICksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IG1heCh6X21hdHJpeCApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMC4wMTI1KSkpICAgJT4lDQogICAgICAgbGF5b3V0KHRpdGxlID0gIkJpdmFyaWF0ZSBTY2F0dGVyIFBsb3QiLA0KICAgICAgICAgICAgICBzY2VuZSA9IGxpc3QoeGF4aXMgPSBsaXN0KHRpdGxlID0gIngxIiksDQogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJ4MiIpLA0KICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiRGVuc2l0eSIpKSwNCiAgICAgICAgICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFDQogICAgICAgICApICU+JSBoaWRlX2NvbG9yYmFyKCkgDQpgYGANCg0KDQpUaGUgMkQgc2NhdHRlciBwbG90IHdpdGggcG9pbnRzIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBsYWJlbHMgaXMgZ2l2ZW4gYmVsb3cuIA0KDQpgYGB7ciAgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KIyMjDQpwbG90KGRhdGEkeDEsIGRhdGEkeDIsIGNvbCA9IGlmZWxzZShkYXRhJHkgPT0gMSwgY29sc1sxMF0sIGNvbHNbMV0pLCBwY2ggPSAxOSwgbWFpbiA9ICJPcmlnaW5hbCBEYXRhIikNCmBgYA0KDQpUaGUgeWVsbG93IHBvaW50cyBpbiB0aGUgbWlkZGxlIGNhbm5vdCBiZSBzZXBhcmF0ZWQgYnkgYSBzdHJhaWdodCBsaW5lIGluIHRoZSBvcmlnaW5hbCB0d28tZGltZW5zaW9uYWwgZmVhdHVyZSBzcGFjZS4gSG93ZXZlciwgYnkgcHJvamVjdGluZyB0aGUgZGF0YSBpbnRvIGEgdGhyZWUtZGltZW5zaW9uYWwgc3BhY2UsIHRoZXkgY2FuIGJlIHNlcGFyYXRlZCB1c2luZyBhIHR3by1kaW1lbnNpb25hbCBwbGFuZSwgZm9ybWluZyBhIGxpbmVhciBkZWNpc2lvbiBib3VuZGFyeSwgYXMgaWxsdXN0cmF0ZWQgaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KI2xpYnJhcnkoTUFTUykNCiNzZXQuc2VlZCgxMjMpDQojIyMjIyMjDQojIyBmdW5jdGlvbiB0byBjYWxjdWxhdGUgdGhlIHRoaXJkIGNvb3JkaW5hdGVzIG9mIHRoZSBiaXZhcmlhdGUgZGVuc2l0eSBzdXJmYWNlDQpmZiA8LSBmdW5jdGlvbih4MSwgeDIpIGRtbm9ybShjYmluZCh4MSwgeDIpLCBtdSwgc2lnbWEpICMgRGVuc2l0eSBmdW5jdGlvbg0KIyMjIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgdGhpcmQgY29vcmRpbmF0ZXMgb2YgdGhlIGludGVyc2VjdGluZyBwbGFuZQ0KZmYwIDwtIGZ1bmN0aW9uKHgxLCB4MikgezAuMDI1ICsgMCp4MSArIDAqeDJ9ICAgIyBEZW5zaXR5IGZ1bmN0aW9uDQojIyMjIyMjDQojIyMgcGxvdCB0aGUgcG9pbnRzIGluIHRoZSAyRCBmZWF0dXJlIHNwYWNlDQp5IDwtIGlmZWxzZShmZih4MTAseDIwKSA+IDAuMDI1LCAxLCAwKSAgICAgIyBkZWZpbmUgdGhlIGxhYmVsIG9mIHRoZSAyRCBwb2ludHMNCiMjIyB3b3JraW5nIGRhdGEgc2V0DQpibi5kYXRhIDwtIGRhdGEuZnJhbWUoeDEgPSB4MTAsIHgyID0geDIwLCB5ID0gYXMuZmFjdG9yKHkpKSAgICMgeSBpcyByZXNwb25zZSBsYWJlbA0KIyMjIHBvaW50cyB3aXRoIGxhYmVsIHkgPSAwDQpibi5kYXRhMCA8LSBibi5kYXRhW2JuLmRhdGEkeSA9PSAwLCBdDQpibi54MS4wIDwtIGJuLmRhdGEwJHgxIA0KYm4ueDIuMCA8LSBibi5kYXRhMCR4Mg0KIyMjIHBvaW50cyB3aXRoIGxhYmVsIHkgPSAxDQpibi5kYXRhMSA8LWJuLmRhdGFbYm4uZGF0YSR5ID09IDEsIF0NCmJuLngxLjEgPC0gYm4uZGF0YTEkeDENCmJuLngyLjEgPC0gYm4uZGF0YTEkeDINCiMjIyBwbG90IHRoZSBiaXZhcmlhdGUgc3VyZmFjZQ0KIyBDcmVhdGUgYSBncmlkIG9mIHBvaW50cyBjb3ZlcmluZyB0aGUgZmVhdHVyZSBzcGFjZQ0KZ3JpZF9yZXNvbHV0aW9uIDwtIDAuMSAgICAgICAgICAjIGdyaWQgc2l6ZQ0KeDFfcmFuZ2UgPC0gc2VxKG1pbihibi5kYXRhJHgxKSwgbWF4KGJuLmRhdGEkeDEpLCBieSA9IGdyaWRfcmVzb2x1dGlvbikNCngyX3JhbmdlIDwtIHNlcShtaW4oYm4uZGF0YSR4MiksIG1heChibi5kYXRhJHgyKSwgYnkgPSBncmlkX3Jlc29sdXRpb24pDQojIENvbnZlcnQgdG8gbWF0cml4IGZvciBwbG90bHkgc3VyZmFjZQ0KIyMgdGhpcmQgY29vcmRpbmF0ZXMgb2YgdGhlIGJpdmFyaWF0ZSBub3JtYWwgZGVuc2l0eSBzdXJmYWNlDQp6X21hdHJpeCA8LSBvdXRlcih4MV9yYW5nZSx4Ml9yYW5nZSwgZmYpICAgIyBoZWlnaHQgb2YgZ3JpZCBwb2ludCBvbiB0aGUgc3VyZmFjZQ0KejAgPC0gb3V0ZXIoeDFfcmFuZ2UsIHgyX3JhbmdlLCBmZjApICAgICAgICMgaW50ZXJzZWN0aW5nIHBsYW5lIGZvciBvYnRhaW5pbmcgY3VydmUgDQojIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGRlY2lzaW9uIGJvdW5kYXJ5DQojIyAzRCBTdXJmYWNlIFBsb3QNCnBsb3RfbHkoKSAgICAlPiUNCiAgICAgICAgICMgYWRkaW5nIGludGVyc2VjdGluZyBwbGFuZQ0KICAgICAgICAgYWRkX3RyYWNlKHggPSAgeDFfcmFuZ2UsICAgIA0KICAgICAgICAgICAgICAgICAgIHkgPSAgeDJfcmFuZ2UsIA0KICAgICAgICAgICAgICAgICAgIHogPSAgejAsDQogICAgICAgICAgICAgICAgICAgY29sb3JzID0gInNreWJsdWUiLA0KICAgICAgICAgICAgICAgICAgIHR5cGU9InN1cmZhY2UiLA0KICAgICAgICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjUpICAgJT4lDQogICAgICAgICAjIGFkZGluZyBwb2ludHMgd2l0aCBsYWJlbCB5ID0gMA0KICAgICAgICAgYWRkX3RyYWNlKHggPSBibi54MS4wLCAgICANCiAgICAgICAgICAgICAgICAgICB5ID0gYm4ueDIuMCwgDQogICAgICAgICAgICAgICAgICAgeiA9IGZmKGJuLngxLjAsIGJuLngyLjApLCANCiAgICAgICAgICAgICAgICAgICBtb2RlID0gIm1hcmtlcnMiLCANCiAgICAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIyJzY2F0dGVyM2QiLCANCiAgICAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSAzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gY29sc1sxXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzeW1ib2wgPSAxMDQpKSAlPiUNCiAgICAgICAgICMgYWRkaW5nIHBvaW50cyB3aXRoIGxhYmVsIHkgPTENCiAgICAgICAgIGFkZF90cmFjZSh4ID0gYm4ueDEuMSwgDQogICAgICAgICAgICAgICAgICAgeSA9IGJuLngyLjEsIA0KICAgICAgICAgICAgICAgICAgIHogPSBmZihibi54MS4xLCBibi54Mi4xKSwNCiAgICAgICAgICAgICAgICAgICBtb2RlID0gIm1hcmtlcnMiLCANCiAgICAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIA0KICAgICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb2xzWzEwXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzeW1ib2wgPSAxMDQpKSAlPiUNCiAgICAgICBsYXlvdXQodGl0bGUgPSAiQml2YXJpYXRlIFNjYXR0ZXIgUGxvdCIsDQogICAgICAgICAgICAgIHNjZW5lID0gbGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSAieDEiKSwNCiAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIngyIiksDQogICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICJEZW5zaXR5IikpLA0KICAgICAgICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UNCiAgICAgICAgICklPiUgaGlkZV9jb2xvcmJhcigpIA0KYGBgDQoNClRoZSBub25saW5lYXIgY3VydmUgZm9ybWVkIGJ5IHRoZSBpbnRlcnNlY3Rpb24gb2YgdGhlIDNEIHN1cmZhY2UgYW5kIHRoZSAyRCBwbGFuZSByZXByZXNlbnRzIHRoZSBkZWNpc2lvbiBib3VuZGFyeS4gSXRzIHByb2plY3Rpb24gb250byB0aGUgb3JpZ2luYWwgMkQgZmVhdHVyZSBzcGFjZSBkZWZpbmVzIHRoZSBub25saW5lYXIgc2VwYXJhdGlvbiBib3VuZGFyeS4NCg0KDQoNCg0KIyMjIEtlcm5lbCBEZW5zaXR5IEVzdGltYXRpb24gQXBwcm9hY2gNCg0KRm9yIHRoZSBzZXBhcmF0aW9uIHByb2JsZW0gaW4gYSB0d28tZGltZW5zaW9uYWwgZmVhdHVyZSBzcGFjZSwgbm9ucGFyYW1ldHJpYyBkZW5zaXR5IGVzdGltYXRpb24gbWV0aG9kcyBjYW4gYmUgdXNlZCB0byBlc3RpbWF0ZSB0aGUgZGVuc2l0eSBzdXJmYWNlLiBUaGUgb3B0aW1hbCBub25saW5lYXIgc2VwYXJhdGlvbiBib3VuZGFyeSBjYW4gdGhlbiBiZSBpZGVudGlmaWVkIHVzaW5nIHRoZSBjb250b3VycyBvZiB0aGUgZXN0aW1hdGVkIGRlbnNpdHkgc3VyZmFjZS4gQmVsb3cgaXMgYW4gZXhhbXBsZSBiYXNlZCBvbiB0aGUgc2FtZSBzaW11bGF0ZWQgZGF0YSBmcm9tIHRoZSBwcmV2aW91cyBzdWJzZWN0aW9uLg0KDQoNCmBgYHtyICBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9DQpzdXJmYWNlID0ga2RlMmQoYm4uZGF0YSR4MSwgYm4uZGF0YSR4MiwgbiA9IDEwMCkNCnBsb3RfbHkoeCA9IHN1cmZhY2UkeCwgDQogICAgICAgIHkgPSBzdXJmYWNlJHksDQogICAgICAgIHogPSBzdXJmYWNlJHosDQogICAgICAgIHR5cGUgPSAic3VyZmFjZSIsDQogICAgICAgIGNvbnRvdXJzID0gbGlzdCggeiA9IGxpc3Qoc2hvdyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsICAgICAgICMgc2V0IGNvbnRvdXIgY29sb3INCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLCAgICAgICAgICAgICAjIHNldCBjb250b3VyIHRoaWNrbmVzcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0ID0gbWluKHpfbWF0cml4ICksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IG1heCh6X21hdHJpeCApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMC4wMTI1KSkpICAgJT4lDQogICAgICAgICAjIGFkZGluZyBpbnRlcnNlY3RpbmcgcGxhbmUNCiAgICAgICAgIGFkZF90cmFjZSh4ID0gIHgxX3JhbmdlLCAgICANCiAgICAgICAgICAgICAgICAgICB5ID0gIHgyX3JhbmdlLCANCiAgICAgICAgICAgICAgICAgICB6ID0gIHowLA0KICAgICAgICAgICAgICAgICAgIGNvbG9ycyA9ICJza3libHVlIiwNCiAgICAgICAgICAgICAgICAgICB0eXBlPSJzdXJmYWNlIiwNCiAgICAgICAgICAgICAgICAgICBvcGFjaXR5ID0gMC41KSAgJT4lIGhpZGVfY29sb3JiYXIoKSANCmBgYA0KDQoNCldlIGNhbiBzZWUgdGhhdCB0aGUgZXN0aW1hdGVkIGRlbnNpdHkgc3VyZmFjZSBpcyBzbGlnaHRseSBkaWZmZXJlbnQgZnJvbSB0aGUgdHJ1ZSBkZW5zaXR5IHN1cmZhY2Ugb2YgdGhlIGJpdmFyaWF0ZSBub3JtYWwgZGlzdHJpYnV0aW9uIGluIHRoZSBwcmV2aW91cyBzdWJzZWN0aW9uLiBUaGUgZm9sbG93aW5nIGNvbnRvdXIgcGxvdCBzaG93cyB0aGUgcG90ZW50aWFsIG5vbmxpbmVhciBzZXBhcmF0aW9uIGJvdW5kYXJ5Lg0KDQoNCmBgYHtyICBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9DQojbGlicmFyeShNQVNTKQ0KIyMjIw0KI3BhcihtZnJvdz1jKDEsMikpDQpjb2xzIDwtIGhjbC5jb2xvcnMoMTAsICJ2aXJpZGlzIikNCnBsb3QoYm4uZGF0YSR4MSwgYm4uZGF0YSR4MiwgY29sID0gaWZlbHNlKGJuLmRhdGEkeSA9PSAxLCBjb2xzWzEwXSwgY29sc1sxXSksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIngxIiwgeWxhYiA9ICJ4MiIsIG1haW4gPSAiQml2YXJpYXRlIEtlcm5lbCBEZW5zaXR5IEVzdGltYXRlZCBTdXJmYWNlIHdpdGggQ29udG91cnMiKQ0KDQpjb250b3VyKGtkZTJkKGJuLmRhdGEkeDEsIGJuLmRhdGEkeDIsIG4gPSAxMDApLCBhZGQgPSBUUlVFLA0KICAgICAgICBsZXZlbCA9IDAuMDEyNSwgbHdkID0gMiwgY29sID0gInB1cnBsZTQiLCBjZXggPSAyKQ0KY29udG91cihrZGUyZChibi5kYXRhJHgxLCBibi5kYXRhJHgyLCBuID0gMTAwKSwgYWRkID0gVFJVRSwNCiAgICAgICAgbGV2ZWwgPSAwLjAyNSwgbHdkID0gMiwgY29sID0gImdvbGQiLCBjZXggPSAyKQ0KYGBgDQoNClRoZSBub25wYXJhbWV0cmljIGFwcHJvYWNoIG9mZmVycyBmbGV4aWJsZSBlc3RpbWF0aW9uIGJ1dCBpcyBzdWJqZWN0IHRvIHBvdGVudGlhbCBtaXNzcGVjaWZpY2F0aW9uLiA8Zm9udCBjb2xvciA9ICJyZWQiPioqSW4gdGhpcyBpbGx1c3RyYXRpdmUgZXhhbXBsZSwgYSBiaXZhcmlhdGUgbm9ybWFsIChHYXVzc2lhbikga2VybmVsIHdhcyB1c2VkIGFzIGEgd2VpZ2h0aW5nIGZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgYWxsIHBvaW50cywgZGVmaW5pbmcgdGhlIGRlbnNpdHkgKG1vdmluZyBhdmVyYWdlKSBpbiB0aHJlZS1kaW1lbnNpb25hbCBzcGFjZSoqPC9mb250Pi4gPGZvbnQgY29sb3IgPSAiYmx1ZSI+KipIb3dldmVyLCBpbiBzdXBwb3J0IHZlY3RvciBtYWNoaW5lcyAoU1ZNKSwgdGhlIEdhdXNzaWFuIGtlcm5lbCBzZXJ2ZXMgYSBkaWZmZXJlbnQgcHVycG9zZTogaXQgbWFwcyBhIGxvd2VyLWRpbWVuc2lvbmFsIGZlYXR1cmUgc3BhY2UgdG8gYSBoaWdoZXItZGltZW5zaW9uYWwgc3BhY2UsIGVuYWJsaW5nIHRoZSB0cmFuc2Zvcm1hdGlvbiBvZiBhIG5vbmxpbmVhciBzZXBhcmF0aW9uIHByb2JsZW0gaW50byBhIGxpbmVhciBvbmUuKio8L2ZvbnQ+DQoNClwNCg0KIyMjIFNWTSB3aXRoIFJCRiBLZXJuZWwgDQoNCldlIG5vdyB1c2UgUiBgc3ZtKClgIGluIHRoZSBSIGxpYnJhcnkgYGUxMDcxYCB0byBmaW5kIHRoZSBkZWNpc2lvbiAoc2VwYXJhdGlvbikgYm91bmRhcnkgb2YgdGhlIGFib3ZlIHNpbXVsYXRlZCBkYXRhIHNldC4gIA0KDQpgYGB7ciAgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KI2xpYnJhcnkoZTEwNzEpDQojbGlicmFyeShnZ3Bsb3QyKQ0KDQojIFRyYWluIFNWTSB3aXRoIFJCRiBrZXJuZWwNCnN2bV9tb2RlbCA8LSBzdm0oeSB+IC4sIGRhdGEgPSBibi5kYXRhLCBrZXJuZWwgPSAicmFkaWFsIiwgZ2FtbWEgPSAxKQ0KIyMNCiMgRGVmaW5lIGEgZ3JpZCBvdmVyIHRoZSBmZWF0dXJlIHNwYWNlDQp4MV9yYW5nZSA8LSBzZXEobWluKGJuLmRhdGEkeDEpLCBtYXgoYm4uZGF0YSR4MSksIGxlbmd0aCA9IDU1KQ0KeDJfcmFuZ2UgPC0gc2VxKG1pbihibi5kYXRhJHgyKSwgbWF4KGJuLmRhdGEkeDIpLCBsZW5ndGggPSA1NSkNCmdyaWQwIDwtIGV4cGFuZC5ncmlkKHgxID0geDFfcmFuZ2UsIHgyID0geDJfcmFuZ2UpDQojIyMjDQojIyMjIFByZWRpY3QgZGVjaXNpb24gdmFsdWVzDQpzdm0ucHJlZCA8LSBwcmVkaWN0KHN2bV9tb2RlbCwgZ3JpZDAsIGRlY2lzaW9uLnZhbHVlcyA9IFRSVUUpDQojIHR1cm4gb3ZlciB0aGUgc3VyZmFjZSBmb3IgZWFzaWVyIHJlYWRpbmcuIGFkZCBhIG5lZ2F0aXZlIHNpZ24gdG8gdGhlIHByZWQgdmFsdWVzLg0KZGVjaXNpb25fdmFsdWVzIDwtIC0gYXR0cmlidXRlcyhzdm0ucHJlZCkkZGVjaXNpb24udmFsdWVzICAgDQpncmlkMCRkZWNpc2lvbl92YWx1ZXMgPC0gZGVjaXNpb25fdmFsdWVzDQp6el9tYXRyaXggPC0gbWF0cml4KGdyaWQwJGRlY2lzaW9uX3ZhbHVlcywgYnlyb3cgPSBUUlVFLCBuY29sPSBsZW5ndGgoeDFfcmFuZ2UpKQ0KIyMjIyMjIyMjIyMjIw0KIyMjIyMjIyMjIyMjIw0KIyMjIDNEIGRhdGEgcG9pbnRzIHBsb3R0aW5nIA0KZml0dGVkLnN2bSA8LSBwcmVkaWN0KHN2bV9tb2RlbCwgYm4uZGF0YVssLTNdLCBkZWNpc2lvbi52YWx1ZXMgPSBUUlVFKQ0KIyByZXZlcnNlIHByZWRpY3RlZCB6IGZvciBkYXRhIHBvaW50cw0KZml0dGVkLnkudmFsIDwtIC0gYXR0cmlidXRlcyhmaXR0ZWQuc3ZtKSRkZWNpc2lvbi52YWx1ZXMgICANCiMgZGF0YSRmaXR0ZWQueS52YWwgPC0gZml0dGVkLnkudmFsDQpkYXRhLnBvaW50cyA8LSBkYXRhLmZyYW1lKHgxID0gYm4uZGF0YSR4MSwgeDIgPSBibi5kYXRhJHgyLCBwcmVkLnkgPWZpdHRlZC55LnZhbCApDQoNCiMjIyBwbG90IHRoZSBwb2ludHMgaW4gdGhlIDJEIGZlYXR1cmUgc3BhY2UNCnkgPC0gaWZlbHNlKGZpdHRlZC55LnZhbCAgPiAwLCAxLCAwKSAgICAgIyBkZWZpbmUgdGhlIGxhYmVsIG9mIHRoZSAyRCBwb2ludHMNCiMjIyB3b3JraW5nIGRhdGEgc2V0ICMgeSBpcyByZXNwb25zZSBsYWJlbA0Kc3ZtLmRhdGEgPC0gZGF0YS5mcmFtZSh4MSA9IHgxMCwgeDIgPSB4MjAsIHp6ID0gZml0dGVkLnkudmFsLCB5ID0gYXMuZmFjdG9yKHkpKSAgIA0KIyMjIHBvaW50cyB3aXRoIGxhYmVsIHkgPSAwDQpzdm0uZGF0YTAgPC0gc3ZtLmRhdGFbc3ZtLmRhdGEkeSA9PSAwLCBdDQpzdm0ueDEuMCA8LSBzdm0uZGF0YTAkeDEgDQpzdm0ueDIuMCA8LSBzdm0uZGF0YTAkeDINCnN2bS56LjAgPC0gc3ZtLmRhdGEwJFgwLjENCiMjIyBwb2ludHMgd2l0aCBsYWJlbCB5ID0gMQ0Kc3ZtLmRhdGExIDwtc3ZtLmRhdGFbc3ZtLmRhdGEkeSA9PSAxLCBdDQpzdm0ueDEuMSA8LSBzdm0uZGF0YTEkeDENCnN2bS54Mi4xIDwtIHN2bS5kYXRhMSR4Mg0Kc3ZtLnouMSA8LSBzdm0uZGF0YTEkWDAuMQ0KIyMjIyMjIyMjIyMjIw0KIyMjIFNlcGFyYXRpbmcgcGxhbmUNCmZmMCA8LSBmdW5jdGlvbih4MSwgeDIpIHswICsgMCp4MSArIDAqeDJ9ICAgIyBEZW5zaXR5IGZ1bmN0aW9uDQp6MCA8LSBvdXRlcih4MV9yYW5nZSwgeDJfcmFuZ2UsIGZmMCkgDQoNCiMjIw0KIyBFeHRyYWN0IHNwZWNpZmljIGNvbnRvdXIgKGUuZy4sIGF0IGxldmVsIHogPSAwLjUpDQpjb250b3VyX2RhdGEgPC0gY29udG91ckxpbmVzKHgxX3JhbmdlLCB4Ml9yYW5nZSwgenpfbWF0cml4LCBsZXZlbHMgPSAwKQ0KDQojIENvbnZlcnQgY29udG91ciBkYXRhIHRvIGEgZGF0YSBmcmFtZQ0KY29udG91cl9kZiA8LSBkby5jYWxsKHJiaW5kLCBsYXBwbHkoY29udG91cl9kYXRhLCBmdW5jdGlvbihjbCkgew0KICBkYXRhLmZyYW1lKHggPSBjbCR4LCB5ID0gY2wkeSwgeiA9IHJlcCgwLjUsIGxlbmd0aChjbCR4KSkpICMgQ29uc3RhbnQgeiBsZXZlbA0KfSkpDQojIyMjIyMjIyMjIyMNCg0KIyMgM0QgU3VyZmFjZSBQbG90DQpwbG90X2x5KHggPSB4MV9yYW5nZSwgDQogICAgICAgIHkgPSB4Ml9yYW5nZSwNCiAgICAgICAgeiA9IHp6X21hdHJpeCwgDQogICAgICAgIHR5cGUgPSAic3VyZmFjZSIsDQogICAgICAgIGNvbnRvdXJzID0gbGlzdCggeiA9IGxpc3Qoc2hvdyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImJyb3duIiwgICAgICAgIyBzZXQgY29udG91ciBjb2xvcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gNCwgICAgICAgICAgICAgIyBzZXQgY29udG91ciB0aGlja25lc3MNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjc3RhcnQgPSBtaW4oenpfbWF0cml4ICksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNlbmQgPSBtYXgoenpfbWF0cml4ICksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDApKSkgICAlPiUNCiMpICAgICU+JQ0KICAgICAgICAgIyBhZGRpbmcgaW50ZXJzZWN0aW5nIHBsYW5lDQogICAgICAgICBhZGRfdHJhY2UoeCA9ICB4MV9yYW5nZSwgICAgDQogICAgICAgICAgICAgICAgICAgeSA9ICB4Ml9yYW5nZSwgDQogICAgICAgICAgICAgICAgICAgeiA9ICB6MCwNCiAgICAgICAgICAgICAgICAgICBjb2xvcnMgPSAic3RlZWxibHVlIiwNCiAgICAgICAgICAgICAgICAgICB0eXBlPSJzdXJmYWNlIiwNCiAgICAgICAgICAgICAgICAgICBvcGFjaXR5ID0gMC41KSAgICU+JQ0KICAgICAgICAgIyBhZGRpbmcgcG9pbnRzIHdpdGggbGFiZWwgeSA9IDANCiAgICAgICAgIGFkZF90cmFjZSh4ID0gc3ZtLngxLjAsICAgIA0KICAgICAgICAgICAgICAgICAgIHkgPSBzdm0ueDIuMCwgDQogICAgICAgICAgICAgICAgICAgeiA9IHN2bS56LjAsIA0KICAgICAgICAgICAgICAgICAgIG1vZGUgPSAibWFya2VycyIsIA0KICAgICAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwjInNjYXR0ZXIzZCIsIA0KICAgICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb2xzWzFdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN5bWJvbCA9IDEwNCkpICU+JQ0KICAgICAgICAgIyBhZGRpbmcgcG9pbnRzIHdpdGggbGFiZWwgeSA9MQ0KICAgICAgICAgYWRkX3RyYWNlKHggPSBzdm0ueDEuMSwgDQogICAgICAgICAgICAgICAgICAgeSA9IHN2bS54Mi4xLCANCiAgICAgICAgICAgICAgICAgICB6ID0gc3ZtLnouMSwNCiAgICAgICAgICAgICAgICAgICBtb2RlID0gIm1hcmtlcnMiLCANCiAgICAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIA0KICAgICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb2xzWzEwXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzeW1ib2wgPSAxMDQpKSAlPiUNCiAgICAgICBsYXlvdXQodGl0bGUgPSAiU1ZNIFByZWRpY3RlZCAzRCBTY2F0dGVyIFBsb3QiLA0KICAgICAgICAgICAgICBzY2VuZSA9IGxpc3QoeGF4aXMgPSBsaXN0KHRpdGxlID0gIngxIiksDQogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJ4MiIpLA0KICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiRGVuc2l0eSIpKSwNCiAgICAgICAgICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFDQogICAgICAgICApICU+JSBoaWRlX2NvbG9yYmFyKCkgDQoNCmBgYA0KDQoNClRoZSBub25saW5lYXIgZGVjaXNpb24gYm91bmRhcnkgaXMgZGVwaWN0ZWQgaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuDQoNCg0KYGBge3IgIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0NCmdnc3ZtIDwtIGdncGxvdChkYXRhLCBhZXMoeDEsIHgyLCBjb2xvciA9IHkpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIjAiID0gY29sc1sxXSwgIjEiID0gY29sc1sxMF0pKSArDQogIGxhYnModGl0bGUgPSAiU1ZNIENsYXNzaWZpY2F0aW9uIHdpdGggUkJGIiwNCiAgICAgICB4ID0gIlgtYXhpcyIsDQogICAgICAgeSA9ICJZLWF4aXMiLA0KICAgICAgIGNvbG9yID0gIkNhdGVnb3J5IikgKw0KICBnZW9tX2NvbnRvdXIoZGF0YSA9IGdyaWQwLCBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBhcy5udW1lcmljKGRlY2lzaW9uX3ZhbHVlcykpLCANCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDAsIGNvbG9yID0gInB1cnBsZTQiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dwbG90bHkoZ2dzdm0pDQpgYGANCg0KDQojIEhhbmRpbmcgQ2F0ZWdvcmljYWwgRmVhdHVyZXMNCg0KVGhlIGV4YW1wbGVzIGFuZCBpbGx1c3RyYXRpb25zIG9mIFNWTSBkaXNjdXNzZWQgZWFybGllciBhcmUgYmFzZWQgb24gbnVtZXJpY2FsIGZlYXR1cmVzLiBXaGVuIGRlYWxpbmcgd2l0aCBjYXRlZ29yaWNhbCBmZWF0dXJlcywgd2UgbmVlZCB0byBjb252ZXJ0IHRoZW0gaW50byBudW1lcmljYWwgcmVwcmVzZW50YXRpb25zLiBCZWxvdyBhcmUgc29tZSBjb21tb24gdGVjaG5pcXVlcyBmb3IgaGFuZGxpbmcgY2F0ZWdvcmljYWwgZmVhdHVyZXMgaW4gU1ZNLg0KDQoNCiMjIE9uZS1Ib3QgRW5jb2RpbmcNCk9uZS1ob3QgZW5jb2RpbmcgY29udmVydHMgZWFjaCBjYXRlZ29yaWNhbCBmZWF0dXJlIGludG8gYSBiaW5hcnkgdmVjdG9yLCB3aGVyZSBlYWNoIGNhdGVnb3J5IGlzIHJlcHJlc2VudGVkIGFzIGEgYmluYXJ5IGNvbHVtbi4NCg0KQW4gaWxsdXN0cmF0aXZlIGV4YW1wbGUgaW4gUjoNCg0KYGBge3IgIGVjaG8gPSBUUlVFfQ0KIyBMb2FkIG5lY2Vzc2FyeSBsaWJyYXJ5DQojbGlicmFyeShlMTA3MSkgICMgRm9yIFNWTQ0KI2xpYnJhcnkoY2FyZXQpICAjIEZvciBkYXRhIHByZXByb2Nlc3NpbmcNCg0KIyBFeGFtcGxlIGRhdGFzZXQNCmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgZmVhdHVyZTEgPSBjKCJBIiwgIkIiLCAiQSIsICJDIiksDQogIGZlYXR1cmUyID0gYygiWCIsICJZIiwgIlgiLCAiWiIpLA0KICB0YXJnZXQgPSBjKDEsIDAsIDEsIDApDQopDQoNCiMgT25lLWhvdCBlbmNvZGluZw0KZHVtbXkgPC0gZHVtbXlWYXJzKCIgfiAuIiwgZGF0YSA9IGRhdGEpDQpkYXRhX2VuY29kZWQgPC0gZGF0YS5mcmFtZShwcmVkaWN0KGR1bW15LCBuZXdkYXRhID0gZGF0YSkpDQojIFZpZXcgZW5jb2RlZCBkYXRhDQpwcmludChkYXRhX2VuY29kZWQpDQojIFRyYWluIFNWTQ0KI3N2bV9tb2RlbCA8LSBzdm0odGFyZ2V0IH4gLiwgZGF0YSA9IGRhdGFfZW5jb2RlZCwga2VybmVsID0gImxpbmVhciIpDQojc3VtbWFyeShzdm1fbW9kZWwpDQpgYGANCg0KT25lLWhvdCBlbmNvZGluZyBpcyB0aGUgbW9zdCBjb21tb24gbWV0aG9kIGJ1dCBjYW4gbGVhZCB0byBoaWdoIGRpbWVuc2lvbmFsaXR5IGZvciBmZWF0dXJlcyB3aXRoIG1hbnkgY2F0ZWdvcmllcy4NCg0KDQojIyBMYWJlbCBFbmNvZGluZw0KDQpMYWJlbCBlbmNvZGluZyBhc3NpZ25zIGEgdW5pcXVlIGludGVnZXIgdG8gZWFjaCBjYXRlZ29yeS4gVGhpcyBpcyB1c2VmdWwgZm9yIG9yZGluYWwgY2F0ZWdvcmljYWwgZmVhdHVyZXMuDQoNCkFuIGlsbHVzdHJhdGl2ZSBleGFtcGxlIGluIFI6DQoNCmBgYHtyICBlY2hvID0gVFJVRX0NCiMgRXhhbXBsZSBkYXRhc2V0DQpkYXRhIDwtIGRhdGEuZnJhbWUoDQogIGZlYXR1cmUxID0gYygiTG93IiwgIk1lZGl1bSIsICJIaWdoIiwgIkxvdyIpLA0KICBmZWF0dXJlMiA9IGMoIlNtYWxsIiwgIkxhcmdlIiwgIk1lZGl1bSIsICJTbWFsbCIpLA0KICB0YXJnZXQgPSBjKDEsIDAsIDEsIDApDQopDQoNCiMgTGFiZWwgZW5jb2RpbmcNCmRhdGEkZmVhdHVyZTEgPC0gYXMubnVtZXJpYyhmYWN0b3IoZGF0YSRmZWF0dXJlMSwgbGV2ZWxzID0gYygiTG93IiwgIk1lZGl1bSIsICJIaWdoIikpKQ0KZGF0YSRmZWF0dXJlMiA8LSBhcy5udW1lcmljKGZhY3RvcihkYXRhJGZlYXR1cmUyLCBsZXZlbHMgPSBjKCJTbWFsbCIsICJNZWRpdW0iLCAiTGFyZ2UiKSkpDQoNCiMgVmlldyBlbmNvZGVkIGRhdGENCnByaW50KGRhdGEpDQoNCiMgVHJhaW4gU1ZNDQojc3ZtX21vZGVsIDwtIHN2bSh0YXJnZXQgfiAuLCBkYXRhID0gZGF0YSwga2VybmVsID0gImxpbmVhciIpDQojc3VtbWFyeShzdm1fbW9kZWwpDQpgYGANCg0KTGFiZWwgZW5jb2RpbmcgaXMgc3VpdGFibGUgZm9yIG9yZGluYWwgZGF0YSBidXQgbWF5IGludHJvZHVjZSB1bmludGVuZGVkIG9yZGluYWxpdHkgZm9yIG5vbWluYWwgZGF0YS4NCg0KDQojIyBGcmVxdWVuY3kgRW5jb2RpbmcNCg0KRnJlcXVlbmN5IGVuY29kaW5nIHJlcGxhY2VzIGVhY2ggY2F0ZWdvcnkgd2l0aCBpdHMgZnJlcXVlbmN5IGluIHRoZSBkYXRhc2V0Lg0KDQpBbiBpbGx1c3RyYXRpdmUgZXhhbXBsZSBpbiBSOg0KDQpgYGB7ciAgZWNobyA9IFRSVUV9DQojIEV4YW1wbGUgZGF0YXNldA0KZGF0YSA8LSBkYXRhLmZyYW1lKA0KICBmZWF0dXJlMSA9IGMoIkEiLCAiQiIsICJBIiwgIkMiKSwNCiAgZmVhdHVyZTIgPSBjKCJYIiwgIlkiLCAiWCIsICJaIiksDQogIHRhcmdldCA9IGMoMSwgMCwgMSwgMCkNCikNCg0KIyBGcmVxdWVuY3kgZW5jb2RpbmcNCmZyZXFfZW5jb2RpbmcgPC0gZnVuY3Rpb24oY29sdW1uKSB7DQogIGZyZXEgPC0gdGFibGUoY29sdW1uKQ0KICByZXR1cm4oYXMubnVtZXJpYyhmcmVxW2NvbHVtbl0pKQ0KfQ0KDQpkYXRhJGZlYXR1cmUxIDwtIGZyZXFfZW5jb2RpbmcoZGF0YSRmZWF0dXJlMSkNCmRhdGEkZmVhdHVyZTIgPC0gZnJlcV9lbmNvZGluZyhkYXRhJGZlYXR1cmUyKQ0KDQojIFZpZXcgZW5jb2RlZCBkYXRhDQpwcmludChkYXRhKQ0KDQojIFRyYWluIFNWTQ0KI3N2bV9tb2RlbCA8LSBzdm0odGFyZ2V0IH4gLiwgZGF0YSA9IGRhdGEsIGtlcm5lbCA9ICJsaW5lYXIiKQ0KI3N1bW1hcnkoc3ZtX21vZGVsKQ0KYGBgDQoNCkZyZXF1ZW5jeSBlbmNvZGluZyBhbmQgdGFyZ2V0IGVuY29kaW5nIGFyZSB1c2VmdWwgZm9yIHJlZHVjaW5nIGRpbWVuc2lvbmFsaXR5IGJ1dCBtYXkgbGVhZCB0byBvdmVyZml0dGluZyBpZiBub3QgcmVndWxhcml6ZWQuDQoNCg0KIyMgVGFyZ2V0IEVuY29kaW5nDQpUYXJnZXQgZW5jb2RpbmcgcmVwbGFjZXMgZWFjaCBjYXRlZ29yeSB3aXRoIHRoZSBtZWFuIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUgZm9yIHRoYXQgY2F0ZWdvcnkuIFRoaXMgaXMgdXNlZnVsIGZvciBzdXBlcnZpc2VkIGxlYXJuaW5nIHRhc2tzLg0KDQpBbiBpbGx1c3RyYXRpdmUgZXhhbXBsZSBpbiBSOg0KDQpgYGB7ciBlY2hvID0gVFJVRX0NCiMgRXhhbXBsZSBkYXRhc2V0DQpkYXRhIDwtIGRhdGEuZnJhbWUoDQogIGZlYXR1cmUxID0gYygiQSIsICJCIiwgIkEiLCAiQyIpLA0KICBmZWF0dXJlMiA9IGMoIlgiLCAiWSIsICJYIiwgIloiKSwNCiAgdGFyZ2V0ID0gYygxLCAwLCAxLCAwKQ0KKQ0KDQojIFRhcmdldCBlbmNvZGluZw0KdGFyZ2V0X2VuY29kaW5nIDwtIGZ1bmN0aW9uKGNvbHVtbiwgdGFyZ2V0KSB7DQogIG1lYW5fdGFyZ2V0IDwtIHRhcHBseSh0YXJnZXQsIGNvbHVtbiwgbWVhbikNCiAgcmV0dXJuKG1lYW5fdGFyZ2V0W2NvbHVtbl0pDQp9DQoNCmRhdGEkZmVhdHVyZTEgPC0gdGFyZ2V0X2VuY29kaW5nKGRhdGEkZmVhdHVyZTEsIGRhdGEkdGFyZ2V0KQ0KZGF0YSRmZWF0dXJlMiA8LSB0YXJnZXRfZW5jb2RpbmcoZGF0YSRmZWF0dXJlMiwgZGF0YSR0YXJnZXQpDQoNCiMgVmlldyBlbmNvZGVkIGRhdGENCnByaW50KGRhdGEpDQoNCiMgVHJhaW4gU1ZNDQojc3ZtX21vZGVsIDwtIHN2bSh0YXJnZXQgfiAuLCBkYXRhID0gZGF0YSwga2VybmVsID0gImxpbmVhciIpDQojc3VtbWFyeShzdm1fbW9kZWwpDQpgYGANCg0KDQojIFNWTSBDbGFzc2lmaWNhdGlvbiBBbGdvcml0aG1zDQoNCg0KSGFyZCBtYXJnaW4gYW5kIHNvZnQgbWFyZ2luIGFyZSBpbmRlZWQgY29yZSBjb25jZXB0cyBpbiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNKSwgYW5kIHRoZXkgZGV0ZXJtaW5lIGhvdyBzdHJpY3RseSB0aGUgbW9kZWwgZW5mb3JjZXMgdGhlIHNlcGFyYXRpb24gb2YgZGF0YSBwb2ludHMgaW50byB0aGVpciByZXNwZWN0aXZlIGNsYXNzZXMuIFRoZXkgYXJlIGNlbnRyYWwgdG8gdW5kZXJzdGFuZGluZyBob3cgU1ZNcyBoYW5kbGUgbGluZWFybHkgc2VwYXJhYmxlIGFuZCBub24tbGluZWFybHkgc2VwYXJhYmxlIGRhdGEuIA0KDQoNCkZvciBlYXNlIG9mIHByZXNlbnRhdGlvbiwgd2UgdXNlIHRoZSBsaW5lYXIgU1ZNIGZvciBjbGFzc2lmaWNhdGlvbiBhcyBhbiBleGFtcGxlIHRvIGV4cGxhaW4gdGhlIGNvbmNlcHQgb2Ygc29mdCBhbmQgaGFyZCBtYXJnaW5zIGFuZCB0aGUgZm9ybXVsYXRpb24gb2YgU1ZNLiAgTm90ZSB0aGF0IHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHR3byBwYXJhbGxlbCBoeXBlcnBsYW5lcyBpbiBhIGstZGltZW5zaW9uYWwgc3BhY2UsIGRlbm90ZWQgYnkNCg0KJCQNCkxfMTogXCAgYV8xIHhfMSArYV8yIHhfMiArIFxjZG90cyArIGFfayB4X2sgKyBkXzEgPSAwDQokJA0KYW5kDQoNCiQkDQpMXzI6IFwgIGFfMSB4XzEgK2FfMiB4XzIgKyBcY2RvdHMgKyBhX2sgeF9rICsgZF8yID0gMCwNCiQkDQoNCmlzIGdpdmVuIGJ5DQoNCiQkDQp8TF8xTF8yfCA9IFxmcmFje3xkXzItZF8xfH17XHNxcnR7YV8xXjIgKyBhXzJeMiArIFxjZG90cyArIGFfa14yfX0NCiQkDQoNCg0KIyMgIEhhcmQgTWFyZ2luIFNWTQ0KDQpXaXRob3V0IGxvc3Mgb2YgZ2VuZXJhbGl0eSwgd2UgY29uc2lkZXIgdGhlIGxpbmVhciBzZXBhcmF0aW9uIHByb2JsZW0gaW4gMiBkaW1lbnNpb25hbCBmZWF0dXJlIHNwYWNlLg0KDQpUaGUgZ29hbCBpcyB0byBmaW5kIHRoZSBoeXBlcnBsYW5lIHRoYXQgbWF4aW1pemVzIHRoZSBtYXJnaW4gKHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gKipwYXJhbGxlbCBoeXBlcnBsYW5lcyoqIGFuZCB0aGUgbmVhcmVzdCBkYXRhIHBvaW50cyBmcm9tIGVhY2ggY2xhc3MpIHdoaWxlIGVuc3VyaW5nIHRoYXQgYWxsIGRhdGEgcG9pbnRzIGFyZSBjb3JyZWN0bHkgY2xhc3NpZmllZC4gIEluIHRoZSB0d28gZGltZW5zaW9uYWwgZmVhdHVyZSBzcGFjZSwgdGhlIHR3byAqKnBhcmFsbGVsIGh5cGVycGxhbmVzKiogYXJlIGRlbm90ZWQgYnkgcmVzcGVjdGl2ZWx5DQoNCiQkDQp3XzF4XzEgKyB3XzIgeF8yICsgYiA9ICsxICBcICBcICBcdGV4dHthbmR9IFwgXCAgd18xeF8xICsgd18yeF8yICsgYiA9IC0xLg0KJCQNCg0KVGhlIGNvZWZmaWNpZW50cyBvZiAkeF8xJCBhbmQgJHhfMiQgb2YgdHdvIGV4cHJlc3Npb25zIGFyZSBpZGVudGljYWwgYmVjYXVzZSB0aGV5IGFyZSBwYXJhbGxlbC4gV2l0aCB0aGUgYWJvdmUgKm5vbi1zdGFuZGFyZCBub3RhdGlvbiosICBgdGhlIGh5cGVycGxhbmVgICR3XzF4XzEgKyB3XzIgeF8yICsgYiA9ICsxJCBpcyBjbG9zZXIgdG8gdGhlIGdyb3VwIHdpdGggbGFiZWwgKiokeSA9ICsxJCoqLCB3aGlsZSAkd18xeF8xICsgd18yIHhfMiArIGIgPSAtMSQgaXMgY2xvc2VyIHRvIHRoZSBncm91cCB3aXRoIGxhYmVsICoqJHkgPSAtMSQqKi4gQ29uc2VxdWVudGx5LCAkd18xeF8xICsgd18yIHhfMiArIGIgPSArMSQgaXMgcmVmZXJyZWQgdG8gYXMgdGhlICoqUG9zaXRpdmUgTWFyZ2luIEh5cGVycGxhbmUqKiBhbmQgJHdfMXhfMSArIHdfMiB4XzIgKyBiID0gLTEkIGFzIHRoZSAqKk5lZ2F0aXZlIE1hcmdpbiBIeXBlcnBsYW5lKiouDQoNCg0KDQoNCklmIHdlIGludHJvZHVjZSB0aGUgZm9sbG93aW5nIGNvbHVtbiB2ZWN0b3Igbm90YXRpb25zDQoNCiQkDQpcbWF0aGJme3d9ID0gXGJlZ2lue2JtYXRyaXh9IA0KICAgICAgICAgICAgICAgICAgICAgICAgd18xICAgXFwNCiAgICAgICAgICAgICAgICAgICAgICAgIHdfMiAgDQogICAgICAgICAgICAgICAgICBcZW5ke2JtYXRyaXh9ICBcIFwgXHRleHR7YW5kfSBcIFwgDQpcbWF0aGJme3h9ICA9IFxiZWdpbntibWF0cml4fSANCiAgICAgICAgICAgICAgICAgICAgICAgIHhfMSBcXA0KICAgICAgICAgICAgICAgICAgICAgICAgeF8yDQogICAgICAgICAgICAgICAgICBcZW5ke2JtYXRyaXh9LA0KJCQNCg0Kd2UgY2FuIHJlLWV4cHJlc3MgaW4gdGhlIGZvbGxvd2luZyB2ZWN0b3IgZm9ybQ0KDQokJA0KIFxtYXRoYmZ7d15UeH0gKyBiID0gMSAgXCAgXCAgXHRleHR7YW5kfSBcIFwgIFxtYXRoYmZ7d15UeH0gKyBiID0gLTEuDQokJA0KDQoNClVzaW5nIHRoZSBhYm92ZSBub3RhdGlvbnMsIHdlIHN1bW1hcml6ZSB0aGUgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGh5cGVycGxhbmVzIGluIHRoZSBmb2xsb3dpbmcgZmlndXJlLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNjAlIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9IYXJkTWFyZ2luLnBuZyIpDQpgYGANCg0KQmFzZWQgb24gdGhlIGZvcm11bGEgb2YgZGlzdGFuY2UgYmV0d2VlbiB0d28gcGFyYWxsZWwgbGluZXMgKGkuZS4sIHRoZSAyRCBoeXBlcnBsYW5lcyksIHRoZSBwcm9ibGVtIG9mIG1heGltaXppbmcgdGhlIG1hcmdpbiAkMi98fFxtYXRoYmZ7d318fF8yJCBpcyBleHByZXNzZWQgaW4gdGhlIGZvbGxvd2luZyBlcXVpdmFsZW50IGV4cHJlc3Npb25zDQoNCiQkDQpcbWF4X3tcbWF0aGJme3d9LGJ9XGZyYWN7Mn17fHxcbWF0aGJme3d9fHxeMl8yfSBccmlnaHRhcnJvdyBcbWF4X3tcbWF0aGJme3d9LGJ9XGZyYWN7MX17fHxcbWF0aGJme3d9fHxeMl8yfSBccmlnaHRhcnJvdyBcbWluX3tcbWF0aGJme3d9LGJ9fHxcbWF0aGJme3d9fHxeMl8yLg0KJCQNCg0KVGhhdCBpcywgdGhlIGFib3ZlIG9wdGltaXphdGlvbiBwcm9ibGVtIGlzIHRvIGVzdGltYXRlICRcbWF0aGJme3d9ID0gKHdfMSwgd18yKSQgYW5kICRiJCB0aGF0ICoqbWF4aW1pemUqKiB0aGUgbWFyZ2luICQyL3x8XG1hdGhiZnt3fXx8XzIkIG9yICoqbWluaW1pemUqKiAkfHxcbWF0aGJme3d9fHxfMiQuIFRoaXMgb3B0aW1pemF0aW9uIHdpbGwgYmUgcGVyZm9ybWVkIHVzaW5nIHNvZnR3YXJlIHByb2dyYW1zIHN1Y2ggYXMgUiBmdW5jdGlvbiBgc3ZtKClgIGluIFIgbGlicmFyeSBgTUFTU2AuIA0KDQpPbmNlICRcbWF0aGJme3d9ID0gKHdfMSwgd18yKSQgYW5kICRiJCBhcmUgZXN0aW1hdGVkIGZyb20gdGhlIGRhdGEsIHRoZSAqKmNlbnRlciBoeXBlcnBsYW5lKiogYmV0d2VlbiB0aGUgdHdvIHBhcmFsbGVsIGh5cGVycGxhbmVzLCAqKnRoZSBkZXNpcmVkIGRlY2lzaW9uIGJvdW5kYXJ5IC0gJFxtYXRoYmZ7d15UeH0gKyBiID0gMCQgKiosIGlzIHVuaXF1ZWx5IGRldGVybWluZWQuDQoNCg0KRmluYWxseSwgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbiBvZiBtYXhpbWl6aW5nIGhhcmQtbWFyZ2luIGluIFNWTSBpcyBnaXZlbiBieQ0KDQokJA0KXG1pbl97XG1hdGhiZnt3fSxifSB8fFxtYXRoYmZ7d318fF4yDQokJA0KDQpzdWJqZWN0aW5nIHRvIHRoZSBjb25zdHJhaW50IHRoYXQgYWxsIGRhdGEgcG9pbnRzIGFyZSBjb3JyZWN0bHkgY2xhc3NpZmllZA0KDQokJA0KeV9pKFxtYXRoYmZ7d31eVFxtYXRoYmZ7eH1faSArIGIpIFxnZSAxIFwgXCBcdGV4dHtmb3IgYW55fSBcIFwgaS4gDQokJA0KDQp3aGVyZSAkeV9pJCBpcyB0aGUgbGFiZWwgb2YgJGkkLXRoIGRhdGEgcG9pbnQgdGFraW5nIHZhbHVlcyAkKzEkIG9yICQtMSQuIA0KDQoNClwNCg0KIyMgU29mdCBNYXJnaW46IEMtY2xhc3NpZmljYXRpb24NCg0KDQpTb2Z0IG1hcmdpbiBTVk0gaXMgdXNlZCB3aGVuIHRoZSBkYXRhIGlzICoqbm90IGxpbmVhcmx5IHNlcGFyYWJsZSoqIG9yIGNvbnRhaW5zIG5vaXNlLiA8Zm9udCBjb2xvciA9ICJyZWQiPkl0IGFsbG93cyBmb3Igc29tZSBtaXNjbGFzc2lmaWNhdGlvbiBieSBpbnRyb2R1Y2luZyAqKnNsYWNrIHZhcmlhYmxlcyAoJFx4aV9pJCkqKiB0aGF0IG1lYXN1cmUgdGhlIGRlZ3JlZSBvZiBtaXNjbGFzc2lmaWNhdGlvbiBmb3IgZWFjaCBkYXRhIHBvaW50PC9mb250Pi4gVGhlIGdvYWwgaXMgdG8gbWF4aW1pemUgdGhlIG1hcmdpbiB3aGlsZSBtaW5pbWl6aW5nIHRoZSBzdW0gb2YgdGhlc2Ugc2xhY2sgdmFyaWFibGVzLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNjAlIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9Tb2Z0TWFyZ2luLnBuZyIpDQpgYGANCg0KV2UgY2FuIHNlZSBmcm9tIHRoZSBhYm92ZSBmaWd1cmUgdGhhdCAkMCA8IFx4aV8xIDwgMSQgYW5kICRceGlfMiwgXHhpXzMgPiAxJC4NCg0KDQpUaGUgZXN0aW1hdGlvbiBvZiB0aGUgdW5rbm93biBwYXJhbWV0ZXJzIGluIHRoZSBTVk0gYWxnb3JpdGhtIGlzIHRoZSBmb2xsb3dpbmcgb3B0aW1pemF0aW9uLg0KDQokJA0KXG1pbl97XG1hdGhiZnt3fSxifSBcbGVmdCggfHxcbWF0aGJme3d9fHxeMiArIENcc3VtX3tpPTF9Xm4gXHhpX2lccmlnaHQpDQokJA0KDQpTdWJqZWN0IHRvIHRoZSBjb25zdHJhaW50IHRoYXQgYWxsIGRhdGEgcG9pbnRzIGFyZSBjb3JyZWN0bHkgY2xhc3NpZmllZA0KDQokJA0KeV9pKFxtYXRoYmZ7d31eVFxtYXRoYmZ7eH1faSArIGIpIFxnZSAxIC0gXHhpX2kgXCBcIFx0ZXh0e2ZvciBhbnl9IFwgXCBpLiANCiQkDQoNCndoZXJlICRceGlfaSBcZ2UgMCQgYW5kICAkeV9pJCBpcyB0aGUgbGFiZWwgb2YgJGkkLXRoIGRhdGEgcG9pbnQgdGFraW5nIHZhbHVlcyAkKzEkIG9yICQtMSQuICRDJCBpcyBhIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlciB0aGF0IGNvbnRyb2xzIHRoZSB0cmFkZS1vZmYgYmV0d2VlbiBtYXhpbWl6aW5nIHRoZSBtYXJnaW4gYW5kIG1pbmltaXppbmcgdGhlIGNsYXNzaWZpY2F0aW9uIGVycm9yLg0KDQoNClRoZSAqKnNsYWNrIHZhcmlhYmxlICR4aV9pJCoqIGlzIGEgbWVhc3VyZSBvZiBob3cgbXVjaCBhIHBvaW50IHZpb2xhdGVzIHRoZSBoYXJkIG1hcmdpbiBjb25zdHJhaW50LiANCg0KKiBDb3JyZWN0bHkgY2xhc3NpZmllZCBwb2ludHMgb3V0c2lkZSB0aGUgbWFyZ2luIHdpbGwgaGF2ZSAkXHhpX2kgPSAwJC4NCg0KKiBDb3JyZWN0bHkgY2xhc3NpZmllZCBwb2ludHMgd2l0aGluIHRoZSBtYXJnaW4gd2lsbCBoYXZlICQwIDwgXHhpX2kgPCAxJC4NCg0KKiBNaXNjbGFzc2lmaWVkIHBvaW50cyB3aWxsIGhhdmUgJFx4aV9pID4xJC4NCg0KVGhlIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmUgYmFzZWQgb24gbWF4aW1pemluZyBzb2Z0IG1hcmdpbiBpcyBhbHNvIGNhbGxlZCAqKkMtQ2xhc3NpZmljYXRpb24qKiB0byBoaWdobGlnaHQgdGhlIGltcG9ydGFuY2Ugb2YgdGhlIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlciAkQyQuICANCg0KDQpUaGUgcmVndWxhcml6YXRpb24gcGFyYW1ldGVyICRDJCBpbiBTVk0gaXMgaW5kZWVkIGEgKipoeXBlcnBhcmFtZXRlcioqIHRoYXQgbmVlZHMgdG8gYmUgdHVuZWQgdG8gYWNoaWV2ZSB0aGUgYmVzdCBwZXJmb3JtYW5jZSBmb3IgYSBzcGVjaWZpYyBkYXRhc2V0LiBJdCBpcyBub3Qgc29tZXRoaW5nIHRoYXQgaXMgbGVhcm5lZCBkdXJpbmcgdHJhaW5pbmcgYnV0IHJhdGhlciBzZXQgYmVmb3JlIHRyYWluaW5nIGJlZ2lucy4gDQoNCg0KKiA8Zm9udCBjb2xvciA9ICJibHVlIj4kQyQgY29udHJvbHMgdGhlIHBlbmFsdHkgZm9yIG1pc2NsYXNzaWZpZWQgb3IgbWFyZ2luLXZpb2xhdGluZyBkYXRhIHBvaW50cyBpbiBzb2Z0IG1hcmdpbiBTVk0uPC9mb250Pg0KKiA8Zm9udCBjb2xvciA9ICJibHVlIj5BIGxhcmdlciAkQyQgaW1wb3NlcyBhIGhpZ2hlciBwZW5hbHR5IGZvciBtaXNjbGFzc2lmaWNhdGlvbnMsIGxlYWRpbmcgdG8gYSBuYXJyb3dlciBtYXJnaW4gYW5kIHN0cmljdGVyIGNsYXNzaWZpY2F0aW9uIChjbG9zZXIgdG8gaGFyZCBtYXJnaW4gU1ZNKS48L2ZvbnQ+DQoqIDxmb250IGNvbG9yID0gImJsdWUiPkEgc21hbGxlciAkQyQgYWxsb3dzIG1vcmUgbWlzY2xhc3NpZmljYXRpb25zLCByZXN1bHRpbmcgaW4gYSB3aWRlciBtYXJnaW4gYW5kIGEgbW9yZSBmbGV4aWJsZSBtb2RlbC48L2ZvbnQ+DQoNCg0KUHJvcGVyIHR1bmluZyBvZiAkQyQgaXMgY3J1Y2lhbCBiZWNhdXNlIGl0IGRpcmVjdGx5IGluZmx1ZW5jZXMgdGhlIHRyYWRlLW9mZiBiZXR3ZWVuIG1heGltaXppbmcgdGhlIG1hcmdpbiBhbmQgbWluaW1pemluZyBjbGFzc2lmaWNhdGlvbiBlcnJvcnMuDQoNCg0KDQojIyAkXG51JCAtIENsYXNzaWZpY2F0aW9uDQoNClVubGlrZSBDLWNsYXNzaWZpY2F0aW9uIFNWTSBpbiB3aGljaCB0aGUgdHJhZGUtb2ZmIGJldHdlZW4gbWF4aW1pemluZyB0aGUgbWFyZ2luIGFuZCBtaW5pbWl6aW5nIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9ycyBpcyBjb250cm9sbGVkIGJ5IHRoZSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIgJEMkLCBJbiAkXG51JC1TVk0sIHRoZSB0cmFkZS1vZmYgaXMgY29udHJvbGxlZCBieSB0aGUgcGFyYW1ldGVyICRcbnUkLiBUaGUgb2JqZWN0aXZlIGZ1bmN0aW9uIGlzIGRlZmluZWQgYXMNCg0KJCQNClxtaW5fe1xtYXRoYmZ7d30sIGIsIFxtYXRoYmZ7XHhpfSwgXHJob30gXGxlZnQoIHx8XG1hdGhiZnt3fXx8XjIgLSBcbnUgXHJobyArXGZyYWN7MX17Tn1cc3VtX3tpPTF9Xk4gXHhpX2lccmlnaHQpDQokJA0Kc3ViamVjdHMgY29uc3RyYWludHMNCg0KJCQNCnlfaShcbWF0aGJme3d9XlQgXG1hdGhiZnt4fV9pICsgYikgXGdlIFxyaG8gLSBcbWF0aGJme1x4aX1faSwgXCBcIFxtYXRoYmZ7XHhpfV9pIFxnZSAwLCBcIFwgXHRleHR7YW5kfSBcIFwgXHJobyA+IDAsDQokJA0KDQp3aGVyZSANCg0KKiAkfHxcbWF0aGJme3d9fHxeMiQgcmVmbGVjdCB0aGUgbWFyZ2luIGFzIGluIEMtU1ZNLA0KKiAkLVxudSBccmhvJCBhZGp1c3RzIHRoZSBtYXJnaW4gIHdpZHRoICRccmhvJCwNCiogJC1cc3VtX3tpPTF9Xk5ceGlfaS9OJCBwZW5hbGl6ZXMgbWlzY2xhc3NpZmljYXRpb25zIChzbGFjayB2YXJpYWJsZXMgJFx4aV9pJCkuDQoNCiRcbnUkIGlzIHRoZSBoeXBlcnBhcmFtZXRlciBpbiAkXG51JC1jbGFzc2lmaWNhdGlvbiBTVk0gdGhhdCBuZWVkcyB0byBiZSB0dW5lZCB0aHJvdWdoIHZhcmlvdXMgbWV0aG9kcyBpbmNsdWRpbmcgY3Jvc3MtdmFsaWRhdGlvbi4gSXQgY29udHJvbHMgdGhlIGZyYWN0aW9uIG9mIG1hcmdpbiBlcnJvcnMgYW5kIHN1cHBvcnQgdmVjdG9ycy4NCg0KKiAkXG51IFxpbiAoMCwxXSQNCiogQSAqKnNtYWxsICRcbnUkKiogcmVzdWx0cyBpbiBhIHdpZGVyIG1hcmdpbiBidXQgYWxsb3dzIG1vcmUgbWlzY2xhc3NpZmljYXRpb25zLg0KKiBBICoqbGFyZ2UgJFxudSQqKiByZXN1bHRzIGluIGEgbmFycm93ZXIgbWFyZ2luIGJ1dCBhbGxvd3MgZmV3ZXIgbWlzY2xhc3NpZmljYXRpb25zLg0KDQoNCg0KIyMgQy1TVk0gdi5zLiAkXG51JC1TVk0NCg0KVGhlcmUgaXMgYSB0aGVvcmV0aWNhbCByZWxhdGlvbnNoaXAgYmV0d2VlbiAkQyQtU1ZNIGFuZCAkXG51JC1TVk0uIEZvciBhIGdpdmVuIGRhdGFzZXQsIHRoZXJlIGV4aXN0cyBhIG1hcHBpbmcgYmV0d2VlbiAkQyQgYW5kICRcbnUkIHN1Y2ggdGhhdCB0aGUgc29sdXRpb25zIG9mIEMtU1ZNIGFuZCAkXG51JC1TVk0gYXJlIGVxdWl2YWxlbnQuIEhvd2V2ZXIsIHRoaXMgbWFwcGluZyBkZXBlbmRzIG9uIHRoZSBkYXRhc2V0IGFuZCBpcyBub3Qgc3RyYWlnaHRmb3J3YXJkIHRvIGNvbXB1dGUuDQoNCkMtU1ZNIGFuZCAkXG51JC1TVk0gY2FuIHByb2R1Y2UgdGhlIHNhbWUgcmVzdWx0IGlmOg0KDQoqIFRoZSBwYXJhbWV0ZXJzIEMgYW5kICRcbnUkIGFyZSBjaG9zZW4gc3VjaCB0aGF0IHRoZXkgY29ycmVzcG9uZCB0byB0aGUgc2FtZSB0cmFkZS1vZmYgYmV0d2VlbiBtYXJnaW4gc2l6ZSBhbmQgY2xhc3NpZmljYXRpb24gZXJyb3JzLg0KDQoqIFRoZSBkYXRhc2V0IGFuZCBrZXJuZWwgYXJlIHRoZSBzYW1lLg0KDQoqIFRoZSBvcHRpbWl6YXRpb24gYWxnb3JpdGhtIGNvbnZlcmdlcyB0byB0aGUgc2FtZSBzb2x1dGlvbi4NCg0KSW4gcHJhY3RpY2UsIHRoaXMgZXF1aXZhbGVuY2UgaXMgcmFyZWx5IGV4cGxvaXRlZCBiZWNhdXNlICRcbnUkLVNWTSBwcm92aWRlcyBhIG1vcmUgaW50dWl0aXZlIHdheSB0byBjb250cm9sIHRoZSBmcmFjdGlvbiBvZiBzdXBwb3J0IHZlY3RvcnMgYW5kIGVycm9ycywgd2hpbGUgQy1TVk0gcmVxdWlyZXMgdHVuaW5nIEMgdGhyb3VnaCBjcm9zcy12YWxpZGF0aW9uIG9yIG90aGVyIG1ldGhvZHMuDQoNCg0KDQoNCg0KRm9yIHRoZSBjb252ZW5pZW5jZSBvZiBjb21wYXJpc29uLCB3ZSBtYWtlIHRoZSBmb2xsb3dpbmcgdGFibGUgdG8gc2hvdyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gc3VwcG9ydCB2ZWN0b3IgbWFjaGluZXMuDQoNCg0KfCAJfCBDLUNsYXNzaWZpY2F0aW9uCXwgJFxudSQtQ2xhc3NpZmljYXRpb24gfA0KfDotLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS18DQp8SHlwZXJwYXJhbWV0ZXIJfCBVc2VzICRDJCAoY29zdCBwYXJhbWV0ZXIpLgl8IFVzZXMgJFxudSQgKG51IHBhcmFtZXRlcikufA0KfEludGVycHJldGF0aW9uCXwgJEMkIGNvbnRyb2xzIHRoZSBwZW5hbHR5IGZvciBtaXNzLWNsYXNzaWZpY2F0aW9ucy4gfCAkXG51JCBjb250cm9scyB0aGUgZnJhY3Rpb24gb2Ygc3VwcG9ydCB2ZWN0b3JzIGFuZCBtYXJnaW4gZXJyb3JzLnwNCnxSYW5nZQl8IEMgY2FuIHRha2UgYW55IHBvc2l0aXZlIHZhbHVlICh0eXBpY2FsbHkgJEM+MCQpLiB8CSRcbnUkIHJhbmdlcyBiZXR3ZWVuIDAgYW5kIDEgKCQwPFxudSBcbGUgMSQpLg0KfEZvY3VzCXwgRm9jdXNlcyBvbiBtaW5pbWl6aW5nIGNsYXNzaWZpY2F0aW9uIGVycm9ycy4gfAlGb2N1c2VzIG9uIGNvbnRyb2xsaW5nIHRoZSBmcmFjdGlvbiBvZiBzdXBwb3J0IHZlY3RvcnMgYW5kIG1hcmdpbiBlcnJvcnMufA0KfCBNYXJnaW4gQ29udHJvbCB8CUZpeGVkIG1hcmdpbnwgCUFkYXB0aXZlIG1hcmdpbiAoJFxyaG8kKSB8DQp8SW50ZXJwcmV0YWJpbGl0eSB8CUxlc3MgaW50dWl0aXZlIHwJTW9yZSBpbnR1aXRpdmUgfA0KfEVhc2Ugb2YgVHVuaW5nIHwJUmVxdWlyZXMgY2FyZWZ1bCB0dW5pbmcgb2YgQyB0byBiYWxhbmNlIGJpYXMgYW5kIHZhcmlhbmNlLnwJRWFzaWVyIHRvIGludGVycHJldCwgYXMgJFxudSQgZGlyZWN0bHkgcmVsYXRlcyB0byB0aGUgZnJhY3Rpb24gb2Ygc3VwcG9ydCB2ZWN0b3JzLnwNCnwgVXNlIENhc2UgfAlHZW5lcmFsLXB1cnBvc2UgfAlUYXNrcyByZXF1aXJpbmcgY29udHJvbCBvdmVyIHN1cHBvcnQgdmVjdG9ycyB8DQoNCg0KJFxudSQtU1ZNIGlzIGEgcG93ZXJmdWwgYW5kIGludGVycHJldGFibGUgdmFyaWFudCBvZiBTVk0gdGhhdCBwcm92aWRlcyBleHBsaWNpdCBjb250cm9sIG92ZXIgdGhlIGZyYWN0aW9uIG9mIHN1cHBvcnQgdmVjdG9ycyBhbmQgbWFyZ2luIGVycm9ycy4gSXRzIGludHVpdGl2ZSBwYXJhbWV0ZXJpemF0aW9uIGFuZCBhZGFwdGl2ZSBtYXJnaW4gbWFrZSBpdCBhIHZhbHVhYmxlIHRvb2wgZm9yIGNsYXNzaWZpY2F0aW9uLCByZWdyZXNzaW9uLCBhbmQgb3V0bGllciBkZXRlY3Rpb24gdGFza3MuIA0KDQoNClwNCg0KIyMgU1ZNIHdpdGggUg0KDQpTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyAoU1ZNKSBhcmUgYSBwb3B1bGFyIHNldCBvZiBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgdXNlZCBmb3IgY2xhc3NpZmljYXRpb24sIHJlZ3Jlc3Npb24sIGFuZCBvdXRsaWVyIGRldGVjdGlvbi4gSW4gUiwgc2V2ZXJhbCBsaWJyYXJpZXMgcHJvdmlkZSBpbXBsZW1lbnRhdGlvbnMgb2YgU1ZNLCBlYWNoIHdpdGggaXRzIG93biBmZWF0dXJlcyBhbmQgY2FwYWJpbGl0aWVzLiBXZSB3aWxsIHVzZSB0d28gb2YgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBsaWJyYXJpZXM6IGBlMTA3MWAgYW5kIGBrZXJubGFiYC4gIEEgcmVjZW50bHkgZGV2ZWxvcGVkIFIgbGlicmFyeSBgY2FyZXRgIGluY2x1ZGVzIHNvbWUgb2YgdGhlIFIgZnVuY3Rpb25zIGluIHRoZSBhZm9yZW1lbnRpb25lZCBsaWJyYXJpZXMgd2l0aCBzb21lIGFkZGl0aW9uYWwgY29udmVuaWVudCBmdW5jdGlvbnMgZm9yIHBlcmZvcm1pbmcgbWFjaGluZSBsZWFybmluZyB0YXNrcy4gDQoNCiogKiplMTA3MSoqOiBHZW5lcmFsLXB1cnBvc2UgU1ZNIGZvciBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiB0YXNrcy4gSXQgaXMgb25lIG9mIHRoZSBtb3N0IHdpZGVseSB1c2VkIHBhY2thZ2VzIGZvciBTVk0gaW4gUiB0aGF0IFN1cHBvcnRzIHZhcmlvdXMga2VybmVsIGZ1bmN0aW9ucyAobGluZWFyLCBwb2x5bm9taWFsLCByYWRpYWwgYmFzaXMsIHNpZ21vaWQpIGFuZCBwcm92aWRlcyB0b29scyBmb3IgbW9kZWwgdHVuaW5nIGFuZCBjcm9zcy12YWxpZGF0aW9uLg0KICArIGBzdm0oKWA6IEZpdHMgYW4gU1ZNIG1vZGVsIGZvciBjbGFzc2lmaWNhdGlvbiwgcmVncmVzc2lvbiwgb3Igbm92ZWx0eSBkZXRlY3Rpb24uDQogICsgYHR1bmUuc3ZtKClgOiBQZXJmb3JtcyBoeXBlcnBhcmFtZXRlciB0dW5pbmcgKGUuZy4sIGNvc3QsIGdhbW1hKSB1c2luZyBncmlkIHNlYXJjaC4NCg0KDQoqICoqa2VybmxhYioqOiBBZHZhbmNlZCBTVk0gdGFza3Mgd2l0aCBjdXN0b20ga2VybmVscyBvciBzcGVjaWFsaXplZCByZXF1aXJlbWVudHMuIEl0IGlzIGEgY29tcHJlaGVuc2l2ZSBwYWNrYWdlIGZvciBrZXJuZWwtYmFzZWQgbWFjaGluZSBsZWFybmluZywgaW5jbHVkaW5nIFNWTS4gSXQgb2ZmZXJzIGEgd2lkZSByYW5nZSBvZiBrZXJuZWwgZnVuY3Rpb25zIGFuZCBpcyBoaWdobHkgZmxleGlibGUuDQogICsgYGtzdm0oKWA6IEZpdHMgYW4gU1ZNIG1vZGVsIHdpdGggc3VwcG9ydCBmb3IgbXVsdGlwbGUga2VybmVsIHR5cGVzLg0KICArIGBrZXJuZWxNYXRyaXgoKWA6IENvbXB1dGVzIHRoZSBrZXJuZWwgbWF0cml4IGZvciBjdXN0b20ga2VybmVscy4NCg0KKiAqKmNhcmV0Kio6IFN0cmVhbWxpbmVkIFNWTSBtb2RlbGluZyB3aXRoIGF1dG9tYXRlZCB0dW5pbmcgYW5kIGV2YWx1YXRpb24uIEl0IGlzIGEgbWV0YS1wYWNrYWdlIGZvciBtYWNoaW5lIGxlYXJuaW5nIHRoYXQgcHJvdmlkZXMgYSB1bmlmaWVkIGludGVyZmFjZSBmb3IgdmFyaW91cyBtb2RlbHMsIGluY2x1ZGluZyBTVk0uIEl0IHVzZXMgYGUxMDcxYCBvciBga2VybmxhYmAgdW5kZXIgdGhlIGhvb2QgYW5kIHNpbXBsaWZpZXMgbW9kZWwgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb24gYW5kIGludGVncmF0ZXMgd2l0aCBvdGhlciBtYWNoaW5lIGxlYXJuaW5nIHdvcmtmbG93cy4NCiAgKyBgdHJhaW4oKWA6IEZpdHMgYW4gU1ZNIG1vZGVsIHdpdGggaHlwZXJwYXJhbWV0ZXIgdHVuaW5nIGFuZCBjcm9zcy12YWxpZGF0aW9uLg0KDQoNCg0KDQoNCg0KDQo=